zerobounce-sdk 1.1.1 → 2.1.8
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 +4 -4
- data/.dockerignore +17 -0
- data/.env.example +3 -0
- data/.github/auto_assign.yml +5 -0
- data/.github/dependabot.yml +35 -0
- data/.github/workflows/auto_assign_ci.yaml +21 -0
- data/.github/workflows/codeql.yml +105 -0
- data/.github/workflows/publish.yml +131 -0
- data/.github/workflows/sdk_ci.yml +29 -0
- data/.gitignore +6 -0
- data/CHANGELOG.md +12 -0
- data/CODE_OF_CONDUCT.md +1 -6
- data/CONTRIBUTING.md +112 -1
- data/Dockerfile +17 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +24 -12
- data/LICENSE +1 -1
- data/LICENSE.txt +6 -6
- data/README.md +313 -24
- data/SECURITY.md +21 -0
- data/documentation.md +104 -11
- data/lib/zerobounce/api_urls.rb +18 -0
- data/lib/zerobounce/base_request.rb +31 -3
- data/lib/zerobounce/configuration.rb +13 -9
- data/lib/zerobounce/download_type.rb +10 -0
- data/lib/zerobounce/get_file_helper.rb +90 -0
- data/lib/zerobounce/get_file_options.rb +14 -0
- data/lib/zerobounce/mock_request.rb +26 -3
- data/lib/zerobounce/request.rb +27 -3
- data/lib/zerobounce/validate_status.rb +17 -0
- data/lib/zerobounce/validate_sub_status.rb +40 -0
- data/lib/zerobounce/version.rb +1 -1
- data/lib/zerobounce.rb +203 -30
- data/zerobounce.gemspec +7 -3
- metadata +86 -16
- data/.env.sample +0 -3
- data/documentation_es.md +0 -449
data/documentation.md
CHANGED
|
@@ -76,10 +76,13 @@ Zerobounce.api_usage(Date.today, Date.today)
|
|
|
76
76
|
"sub_status_mailbox_quota_exceeded"=>0,
|
|
77
77
|
"sub_status_forcible_disconnect"=>0,
|
|
78
78
|
"sub_status_failed_smtp_connection"=>0,
|
|
79
|
+
"sub_status_accept_all"=>0,
|
|
79
80
|
"sub_status_mx_forward"=>0,
|
|
80
81
|
"sub_status_alternate"=>0,
|
|
81
|
-
"sub_status_blocked"=>0,
|
|
82
82
|
"sub_status_allowed"=>0,
|
|
83
|
+
"sub_status_blocked"=>0,
|
|
84
|
+
"sub_status_gold"=>0,
|
|
85
|
+
"sub_status_role_based_accept_all"=>0,
|
|
83
86
|
"start_date"=>"4/28/2023",
|
|
84
87
|
"end_date"=>"4/28/2023"}
|
|
85
88
|
```
|
|
@@ -265,10 +268,13 @@ Send file
|
|
|
265
268
|
last_name_column: 3,
|
|
266
269
|
gender_column: 4,
|
|
267
270
|
has_header_row: true,
|
|
268
|
-
return_url: nil
|
|
271
|
+
return_url: nil, ### results callback url
|
|
272
|
+
allow_phase_2: true ### optional; validation bulk sendfile only (omit or nil to skip)
|
|
269
273
|
### Zerobounce.validate_file_send(validate_file_path, email_address_column: 1, gender_column: 4)
|
|
270
274
|
```
|
|
271
275
|
|
|
276
|
+
Bulk validation uses `https://bulkapi.zerobounce.net/v2`. See [v2 send file](https://www.zerobounce.net/docs/email-validation-api-quickstart/v2-send-file), [v2 file status](https://www.zerobounce.net/docs/email-validation-api-quickstart/v2-file-status), and [v2 get file](https://www.zerobounce.net/docs/email-validation-api-quickstart/v2-get-file).
|
|
277
|
+
|
|
272
278
|
Check file
|
|
273
279
|
```ruby
|
|
274
280
|
file_id = "75d854a6-565c-49f9-b4c8-b3344480ec4c"
|
|
@@ -281,6 +287,7 @@ Zerobounce.validate_file_check(file_id)
|
|
|
281
287
|
"upload_date"=>"2023-04-28T15:25:41Z",
|
|
282
288
|
"file_status"=>"Greylisted",
|
|
283
289
|
"complete_percentage"=>"0%",
|
|
290
|
+
"file_phase_2_status"=>"N/A", ### when present (validation bulk v2)
|
|
284
291
|
"error_reason"=>nil,
|
|
285
292
|
"return_url"=>nil}
|
|
286
293
|
```
|
|
@@ -293,6 +300,16 @@ Zerobounce.validate_file_get(file_id)
|
|
|
293
300
|
=> "\"email\",\"first\",\"last\",\"gender\",\"ip\",\"ZB Status\",\"ZB Sub Status\",\"ZB Account\",\"ZB Domain\",\"ZB First Name\",\"ZB Last Name\",\"ZB Gender\",\"ZB Free Email\",\"ZB MX Found\",\"ZB MX Record\",\"ZB SMTP Provider\",\"ZB Did You Mean\"\n\"disposable@example.com\",\"First\",\"Last\",\"male\",\"127.0.0.1\",\"do_not_mail\",\"disposable\",\"\",\"\",\"zero\",\"bounce\",\"male\",\"False\",\"true\",\"mx.example.com\",\"example\",\"\"\n\"invalid@example.com\",\"First\",\"Last\",\"male\",\"127.0.0.1\",\"invalid\",\"mailbox_not_found\",\"\",\"\",\"zero\",\"bounce\",\"male\",\"False\",\"true\",\"mx.example.com\",\"example\",\"\"\n\"valid@example.com\",\"First\",\"Last\",\"male\",\"127.0.0.1\",\"valid\",\"\",\"\",\"\",\"zero\",\"bounce\",\"male\",\"False\",\"true\",\"mx.example.com\",\"example\",\"\"\n\"toxic@example.com\",\"First\",\"Last\",\"male\",\"127.0.0.1\",\"do_not_mail\",\"toxic\",\"\",\"\",\"zero\",\"bounce\",\"male\",\"False\",\"true\",\"mx.example.com\",\"example\",\"\"\n\"donotmail@example.com\",\"First\",\"Last\",\"male\",\"127.0.0.1\",\"do_not_mail\",\"role_based\",\"\",\"\",\"zero\",\"bounce\",\"male\",\"False\",\"true\",\"mx.example.com\",\"example\",\"\"\n\"spamtrap@example.com\",\"First\",\"Last\",\"male\",\"127.0.0.1\",\"spamtrap\",\"\",\"\",\"\",\"zero\",\"bounce\",\"male\",\"False\",\"true\",\"mx.example.com\",\"example\",\"\"\n\"abuse@example.com\",\"First\",\"Last\",\"male\",\"127.0.0.1\",\"abuse\",\"\",\"\",\"\",\"zero\",\"bounce\",\"male\",\"False\",\"true\",\"mx.example.com\",\"example\",\"\"\n\"unknown@example.com\",\"First\",\"Last\",\"male\",\"127.0.0.1\",\"unknown\",\"mail_server_temporary_error\",\"\",\"\",\"zero\",\"bounce\",\"male\",\"False\",\"true\",\"mx.example.com\",\"example\",\"\"\n\"catch_all@example.com\",\"First\",\"Last\",\"male\",\"127.0.0.1\",\"catch-all\",\"\",\"\",\"\",\"zero\",\"bounce\",\"male\",\"False\",\"true\",\"mx.example.com\",\"example\",\"\"\n\"antispam_system@example.com\",\"First\",\"Last\",\"male\",\"127.0.0.1\",\"unknown\",\"antispam_system\",\"\",\"\",\"zero\",\"bounce\",\"male\",\"False\",\"true\",\"mx.example.com\",\"example\",\"\"\n\"does_not_accept_mail@example.com\",\"First\",\"Last\",\"male\",\"127.0.0.1\",\"invalid\",\"does_not_accept_mail\",\"\",\"\",\"zero\",\"bounce\",\"male\",\"False\",\"true\",\"mx.example.com\",\"example\",\"\"\n\"exception_occurred@example.com\",\"First\",\"Last\",\"male\",\"127.0.0.1\",\"unknown\",\"exception_occurred\",\"\",\"\",\"zero\",\"bounce\",\"male\",\"False\",\"true\",\"mx.example.com\",\"example\",\"\"\n\"failed_smtp_connection@example.com\",\"First\",\"Last\",\"male\",\"127.0.0.1\",\"unknown\",\"failed_smtp_connection\",\"\",\"\",\"zero\",\"bounce\",\"male\",\"False\",\"true\",\"mx.example.com\",\"example\",\"\"\n\"failed_syntax_check@example.com\",\"First\",\"Last\",\"male\",\"127.0.0.1\",\"invalid\",\"failed_syntax_check\",\"\",\"\",\"zero\",\"bounce\",\"male\",\"False\",\"true\",\"mx.example.com\",\"example\",\"\"\n\"forcible_disconnect@example.com\",\"First\",\"Last\",\"male\",\"127.0.0.1\",\"unknown\",\"forcible_disconnect\",\"\",\"\",\"zero\",\"bounce\",\"male\",\"False\",\"true\",\"mx.example.com\",\"example\",\"\"\n\"global_suppression@example.com\",\"First\",\"Last\",\"male\",\"127.0.0.1\",\"do_not_mail\",\"global_suppression\",\"\",\"\",\"zero\",\"bounce\",\"male\",\"False\",\"true\",\"mx.example.com\",\"example\",\"\"\n\"greylisted@example.com\",\"First\",\"Last\",\"male\",\"127.0.0.1\",\"unknown\",\"greylisted\",\"\",\"\",\"zero\",\"bounce\",\"male\",\"False\",\"true\",\"mx.example.com\",\"example\",\"\"\n\"leading_period_removed@example.com\",\"First\",\"Last\",\"male\",\"127.0.0.1\",\"valid\",\"leading_period_removed\",\"\",\"\",\"zero\",\"bounce\",\"male\",\"False\",\"true\",\"mx.example.com\",\"example\",\"\"\n\"mail_server_did_not_respond@example.com\",\"First\",\"Last\",\"male\",\"127.0.0.1\",\"unknown\",\"mail_server_did_not_respond\",\"\",\"\",\"zero\",\"bounce\",\"male\",\"False\",\"true\",\"mx.example.com\",\"example\",\"\"\n\"mail_server_temporary_error@example.com\",\"First\",\"Last\",\"male\",\"127.0.0.1\",\"unknown\",\"mail_server_temporary_error\",\"\",\"\",\"zero\",\"bounce\",\"male\",\"False\",\"true\",\"mx.example.com\",\"example\",\"\"\n\"mailbox_quota_exceeded@example.com\",\"First\",\"Last\",\"male\",\"127.0.0.1\",\"invalid\",\"mailbox_quota_exceeded\",\"\",\"\",\"zero\",\"bounce\",\"male\",\"False\",\"true\",\"mx.example.com\",\"example\",\"\"\n\"mailbox_not_found@example.com\",\"First\",\"Last\",\"male\",\"127.0.0.1\",\"invalid\",\"mailbox_not_found\",\"\",\"\",\"zero\",\"bounce\",\"male\",\"False\",\"true\",\"mx.example.com\",\"example\",\"\"\n\"no_dns_entries@example.com\",\"First\",\"Last\",\"male\",\"127.0.0.1\",\"invalid\",\"no_dns_entries\",\"\",\"\",\"zero\",\"bounce\",\"male\",\"False\",\"true\",\"mx.example.com\",\"example\",\"\"\n\"possible_trap@example.com\",\"First\",\"Last\",\"male\",\"127.0.0.1\",\"do_not_mail\",\"possible_trap\",\"\",\"\",\"zero\",\"bounce\",\"male\",\"False\",\"true\",\"mx.example.com\",\"example\",\"\"\n\"possible_typo@example.com\",\"First\",\"Last\",\"male\",\"127.0.0.1\",\"invalid\",\"possible_typo\",\"\",\"\",\"zero\",\"bounce\",\"male\",\"False\",\"true\",\"mx.example.com\",\"example\",\"\"\n\"role_based@example.com\",\"First\",\"Last\",\"male\",\"127.0.0.1\",\"do_not_mail\",\"role_based\",\"\",\"\",\"zero\",\"bounce\",\"male\",\"False\",\"true\",\"mx.example.com\",\"example\",\"\"\n\"timeout_exceeded@example.com\",\"First\",\"Last\",\"male\",\"127.0.0.1\",\"unknown\",\"timeout_exceeded\",\"\",\"\",\"zero\",\"bounce\",\"male\",\"False\",\"true\",\"mx.example.com\",\"example\",\"\"\n\"unroutable_ip_address@example.com\",\"First\",\"Last\",\"male\",\"127.0.0.1\",\"invalid\",\"unroutable_ip_address\",\"\",\"\",\"zero\",\"bounce\",\"male\",\"False\",\"true\",\"mx.example.com\",\"example\",\"\"\n\"free_email@example.com\",\"First\",\"Last\",\"male\",\"127.0.0.1\",\"valid\",\"\",\"\",\"\",\"zero\",\"bounce\",\"male\",\"True\",\"true\",\"mx.example.com\",\"example\",\"\"\n\"role_based_catch_all@example.com\",\"First\",\"Last\",\"male\",\"127.0.0.1\",\"do_not_mail\",\"role_based_catch_all\",\"\",\"\",\"zero\",\"bounce\",\"male\",\"False\",\"true\",\"mx.example.com\",\"example\",\"\"\n"
|
|
294
301
|
```
|
|
295
302
|
|
|
303
|
+
Optional [v2 get file](https://www.zerobounce.net/docs/email-validation-api-quickstart/v2-get-file) query parameters: `Zerobounce::GetFileOptions` and `Zerobounce::DownloadType` (`PHASE_1`, `PHASE_2`, `COMBINED`). Use `activity_data` on the options for validation `validate_file_get` only. JSON error bodies (including some HTTP 200 responses) raise `RuntimeError`; use `Zerobounce.get_file_json_indicates_error?(body)` on a raw string if needed.
|
|
304
|
+
|
|
305
|
+
```ruby
|
|
306
|
+
opts = Zerobounce::GetFileOptions.new(
|
|
307
|
+
download_type: Zerobounce::DownloadType::COMBINED,
|
|
308
|
+
activity_data: true
|
|
309
|
+
)
|
|
310
|
+
csv = Zerobounce.validate_file_get(file_id, opts)
|
|
311
|
+
```
|
|
312
|
+
|
|
296
313
|
Delete file
|
|
297
314
|
```ruby
|
|
298
315
|
file_id = "75d854a6-565c-49f9-b4c8-b3344480ec4c"
|
|
@@ -346,13 +363,14 @@ file_id = "89fb7262-b845-4fa1-aa25-e486347ec64e"
|
|
|
346
363
|
=> "89fb7262-b845-4fa1-aa25-e486347ec64e"
|
|
347
364
|
Zerobounce.scoring_file_get(file_id)
|
|
348
365
|
=> "\"email\",\"ZeroBounceQualityScore\"\r\n\"disposable@example.com\",\"0\"\r\n\"invalid@example.com\",\"10\"\r\n\"valid@example.com\",\"10\"\r\n\"toxic@example.com\",\"2\"\r\n\"donotmail@example.com\",\"0\"\r\n\"spamtrap@example.com\",\"0\"\r\n\"abuse@example.com\",\"10\"\r\n\"unknown@example.com\",\"10\"\r\n\"catch_all@example.com\",\"10\"\r\n\"antispam_system@example.com\",\"0\"\r\n\"does_not_accept_mail@example.com\",\"0\"\r\n\"exception_occurred@example.com\",\"0\"\r\n\"failed_smtp_connection@example.com\",\"0\"\r\n\"failed_syntax_check@example.com\",\"0\"\r\n\"forcible_disconnect@example.com\",\"0\"\r\n\"global_suppression@example.com\",\"0\"\r\n\"greylisted@example.com\",\"0\"\r\n\"leading_period_removed@example.com\",\"0\"\r\n\"mail_server_did_not_respond@example.com\",\"0\"\r\n\"mail_server_temporary_error@example.com\",\"0\"\r\n\"mailbox_quota_exceeded@example.com\",\"0\"\r\n\"mailbox_not_found@example.com\",\"0\"\r\n\"no_dns_entries@example.com\",\"0\"\r\n\"possible_trap@example.com\",\"0\"\r\n\"possible_typo@example.com\",\"0\"\r\n\"role_based@example.com\",\"0\"\r\n\"timeout_exceeded@example.com\",\"0\"\r\n\"unroutable_ip_address@example.com\",\"0\"\r\n\"free_email@example.com\",\"0\"\r\n\"role_based_catch_all@example.com\",\"0\""
|
|
366
|
+
### Optional second argument: GetFileOptions with download_type only (activity_data is not sent for scoring getfile)
|
|
349
367
|
```
|
|
350
368
|
|
|
351
369
|
Delete file
|
|
352
370
|
```ruby
|
|
353
371
|
file_id = "89fb7262-b845-4fa1-aa25-e486347ec64e"
|
|
354
372
|
=> "89fb7262-b845-4fa1-aa25-e486347ec64e"
|
|
355
|
-
Zerobounce.
|
|
373
|
+
Zerobounce.scoring_file_delete(file_id)
|
|
356
374
|
=> {"success"=>false, "message"=>"File cannot be found."}
|
|
357
375
|
```
|
|
358
376
|
|
|
@@ -400,6 +418,81 @@ Zerobounce.guessformat("zerobounce.net")
|
|
|
400
418
|
# Zerobounce.guessformat("zerobounce.net", first_name: "John", middle_name: 'Deere', last_name: "Doe")
|
|
401
419
|
```
|
|
402
420
|
|
|
421
|
+
Find Email Address
|
|
422
|
+
```ruby
|
|
423
|
+
Zerobounce.find_email("John", domain: "zerobounce.net")
|
|
424
|
+
=>
|
|
425
|
+
{
|
|
426
|
+
"email": "john@zerobounce.net",
|
|
427
|
+
"email_confidence": "medium",
|
|
428
|
+
"domain": "zerobounce.net",
|
|
429
|
+
"company_name": "ZeroBounce",
|
|
430
|
+
"did_you_mean": "",
|
|
431
|
+
"failure_reason": ""
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
# With additional name information for better accuracy
|
|
435
|
+
Zerobounce.find_email("John", domain: "zerobounce.net", middle_name: "Deere", last_name: "Doe")
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
Find Domain Information
|
|
439
|
+
```ruby
|
|
440
|
+
Zerobounce.find_domain(domain: "zerobounce.net")
|
|
441
|
+
=>
|
|
442
|
+
{
|
|
443
|
+
"domain": "zerobounce.net",
|
|
444
|
+
"company_name": "Hertza, LLC",
|
|
445
|
+
"format": "first.last",
|
|
446
|
+
"confidence": "high",
|
|
447
|
+
"did_you_mean": "",
|
|
448
|
+
"failure_reason": "",
|
|
449
|
+
"other_domain_formats": [
|
|
450
|
+
{"format": "first", "confidence": "high"},
|
|
451
|
+
{"format": "last.first", "confidence": "high"},
|
|
452
|
+
{"format": "lastfirst", "confidence": "high"},
|
|
453
|
+
{"format": "firstl", "confidence": "high"},
|
|
454
|
+
{"format": "lfirst", "confidence": "high"},
|
|
455
|
+
{"format": "firstlast", "confidence": "high"},
|
|
456
|
+
{"format": "last_middle_f", "confidence": "high"},
|
|
457
|
+
{"format": "last", "confidence": "high"},
|
|
458
|
+
{"format": "f.last", "confidence": "medium"},
|
|
459
|
+
{"format": "last-f", "confidence": "medium"},
|
|
460
|
+
{"format": "l.first", "confidence": "medium"},
|
|
461
|
+
{"format": "last_f", "confidence": "medium"},
|
|
462
|
+
{"format": "first.middle.last", "confidence": "medium"},
|
|
463
|
+
{"format": "first-last", "confidence": "medium"},
|
|
464
|
+
{"format": "last.f", "confidence": "medium"},
|
|
465
|
+
{"format": "last_first", "confidence": "medium"},
|
|
466
|
+
{"format": "f-last", "confidence": "medium"},
|
|
467
|
+
{"format": "first.l", "confidence": "medium"},
|
|
468
|
+
{"format": "first-l", "confidence": "medium"},
|
|
469
|
+
{"format": "first_l", "confidence": "medium"},
|
|
470
|
+
{"format": "first_last", "confidence": "medium"},
|
|
471
|
+
{"format": "f_last", "confidence": "medium"},
|
|
472
|
+
{"format": "last-first", "confidence": "medium"},
|
|
473
|
+
{"format": "flast", "confidence": "medium"},
|
|
474
|
+
{"format": "lastf", "confidence": "medium"},
|
|
475
|
+
{"format": "l_first", "confidence": "medium"},
|
|
476
|
+
{"format": "l-first", "confidence": "medium"},
|
|
477
|
+
{"format": "first-middle-last", "confidence": "low"},
|
|
478
|
+
{"format": "firstmlast", "confidence": "low"},
|
|
479
|
+
{"format": "last.middle.first", "confidence": "low"},
|
|
480
|
+
{"format": "last_middle_first", "confidence": "low"},
|
|
481
|
+
{"format": "first_middle_last", "confidence": "low"},
|
|
482
|
+
{"format": "last-middle-first", "confidence": "low"},
|
|
483
|
+
{"format": "first-m-last", "confidence": "low"},
|
|
484
|
+
{"format": "firstmiddlelast", "confidence": "low"},
|
|
485
|
+
{"format": "last.m.first", "confidence": "low"},
|
|
486
|
+
{"format": "lastmfirst", "confidence": "low"},
|
|
487
|
+
{"format": "lastmiddlefirst", "confidence": "low"},
|
|
488
|
+
{"format": "last_m_first", "confidence": "low"},
|
|
489
|
+
{"format": "first.m.last", "confidence": "low"},
|
|
490
|
+
{"format": "first_m_last", "confidence": "low"},
|
|
491
|
+
{"format": "last-m-first", "confidence": "low"}
|
|
492
|
+
]
|
|
493
|
+
}
|
|
494
|
+
```
|
|
495
|
+
|
|
403
496
|
|
|
404
497
|
#### Development
|
|
405
498
|
|
|
@@ -421,17 +514,17 @@ Finished in 6.81 seconds (files took 0.40587 seconds to load)
|
|
|
421
514
|
|
|
422
515
|
##### Test parameters
|
|
423
516
|
The tests use the following environment parameters:
|
|
424
|
-
TEST {unit|live}
|
|
425
|
-
ZEROBOUNCE_API_KEY
|
|
426
|
-
|
|
517
|
+
- **TEST** {unit|live} – Influences whether mocked unit tests are run or the live server is used (credits may be used if you choose to do this).
|
|
518
|
+
- **ZEROBOUNCE_API_KEY** – Your API key; used to make requests to the live server and in mock tests as the valid key sample (any value will work for mock tests).
|
|
519
|
+
|
|
520
|
+
An invalid API key for error-handling tests is hardcoded in the spec; no env var is required.
|
|
427
521
|
|
|
428
|
-
To set them
|
|
522
|
+
To set them:
|
|
429
523
|
```bash
|
|
430
|
-
export ZEROBOUNCE_API_KEY=
|
|
431
|
-
export INCORRECT_API_KEY=thiskeyisinvalidorotherwiseincorrect
|
|
524
|
+
export ZEROBOUNCE_API_KEY=your_api_key_here
|
|
432
525
|
export TEST=unit
|
|
433
526
|
```
|
|
434
527
|
|
|
435
|
-
A .env.
|
|
528
|
+
A .env.example file is provided.
|
|
436
529
|
|
|
437
|
-
Mock tests were generated using webmock and vcr. This means that actual requests were made and recorded in the spec/cassettes with an (at the time) valid API key used for testing purposes. This key has been invalidated in the meantime, however it is provided in the .env.
|
|
530
|
+
Mock tests were generated using webmock and vcr. This means that actual requests were made and recorded in the spec/cassettes with an (at the time) valid API key used for testing purposes. This key has been invalidated in the meantime, however it is provided in the .env.example file for the mock tests to work. If you do not wish to use this key for mocks, you can replace it with any value in the .yml files under spec/cassettes or delete all of them and rerun the tests so that vcr records them with a new key.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Zerobounce
|
|
4
|
+
# API URL constants for Zerobounce endpoints
|
|
5
|
+
#
|
|
6
|
+
# This class provides constant values for different Zerobounce API endpoints
|
|
7
|
+
# that can be accessed publicly by gem users.
|
|
8
|
+
#
|
|
9
|
+
class ApiUrls
|
|
10
|
+
# API URLs
|
|
11
|
+
DEFAULT_URL = 'https://api.zerobounce.net/v2/'
|
|
12
|
+
EU_URL = 'https://api-eu.zerobounce.net/v2/'
|
|
13
|
+
US_URL = 'https://api-us.zerobounce.net/v2/'
|
|
14
|
+
|
|
15
|
+
# Bulk API URLs
|
|
16
|
+
BULK_DEFAULT_URL = 'https://bulkapi.zerobounce.net/v2/'
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -13,6 +13,34 @@ module Zerobounce
|
|
|
13
13
|
|
|
14
14
|
protected
|
|
15
15
|
|
|
16
|
+
# Strips trailing slashes from root URL without using a regex (avoids ReDoS).
|
|
17
|
+
def self.__root_without_trailing_slashes__(root)
|
|
18
|
+
s = root.to_s
|
|
19
|
+
s = s.chomp('/') while s.end_with?('/')
|
|
20
|
+
s
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Resolves and validates filepath to prevent path traversal (e.g. ../../etc/passwd).
|
|
24
|
+
# Returns a canonical path only if the file is under the current directory and is a regular file.
|
|
25
|
+
def self.__safe_file_path__(filepath)
|
|
26
|
+
raise ArgumentError, 'File path is required' if filepath.nil? || filepath.to_s.empty?
|
|
27
|
+
filepath = filepath.to_s
|
|
28
|
+
expanded = File.expand_path(filepath)
|
|
29
|
+
base = File.realpath(Dir.pwd)
|
|
30
|
+
base_with_sep = base + File::SEPARATOR
|
|
31
|
+
unless expanded == base || expanded.start_with?(base_with_sep)
|
|
32
|
+
raise ArgumentError, 'File path must be under the current directory'
|
|
33
|
+
end
|
|
34
|
+
canonical = File.realpath(expanded)
|
|
35
|
+
unless canonical == base || canonical.start_with?(base_with_sep)
|
|
36
|
+
raise ArgumentError, 'File path must be under the current directory'
|
|
37
|
+
end
|
|
38
|
+
unless File.file?(canonical)
|
|
39
|
+
raise ArgumentError, 'File path must point to a regular file'
|
|
40
|
+
end
|
|
41
|
+
canonical
|
|
42
|
+
end
|
|
43
|
+
|
|
16
44
|
def self._get(root, path, params, content_type='application/json')
|
|
17
45
|
|
|
18
46
|
# puts path
|
|
@@ -21,7 +49,7 @@ module Zerobounce
|
|
|
21
49
|
raise ("API key must be assigned") if not Zerobounce.config.apikey
|
|
22
50
|
|
|
23
51
|
params[:api_key] = Zerobounce.config.apikey
|
|
24
|
-
url = "#{root}/#{path}"
|
|
52
|
+
url = "#{Zerobounce::BaseRequest.__root_without_trailing_slashes__(root)}/#{path}"
|
|
25
53
|
|
|
26
54
|
response = RestClient.get(url, {params: params})
|
|
27
55
|
return response
|
|
@@ -32,11 +60,11 @@ module Zerobounce
|
|
|
32
60
|
raise ("API key must be assigned") if not Zerobounce.config.apikey
|
|
33
61
|
|
|
34
62
|
params[:api_key] = Zerobounce.config.apikey
|
|
35
|
-
url = "#{root}/#{path}"
|
|
63
|
+
url = "#{Zerobounce::BaseRequest.__root_without_trailing_slashes__(root)}/#{path}"
|
|
36
64
|
response = nil
|
|
37
65
|
|
|
38
66
|
if filepath or content_type == 'multipart/form-data'
|
|
39
|
-
params[:file] = File.new(filepath, 'rb')
|
|
67
|
+
params[:file] = File.new(Zerobounce::BaseRequest.__safe_file_path__(filepath), 'rb')
|
|
40
68
|
params[:multipart] = true
|
|
41
69
|
response = RestClient.post(url, params)
|
|
42
70
|
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require 'rest-client'
|
|
4
4
|
require 'dotenv'
|
|
5
|
+
require_relative 'api_urls'
|
|
5
6
|
|
|
6
7
|
module Zerobounce
|
|
7
8
|
# Configuration object for Zerobounce.
|
|
@@ -17,20 +18,23 @@ module Zerobounce
|
|
|
17
18
|
# @attr [String] apikey
|
|
18
19
|
# A Zerobounce API key.
|
|
19
20
|
#
|
|
21
|
+
# @attr [String] api_root_url
|
|
22
|
+
# The Zerobounce API root URL. Defaults to ApiUrls::DEFAULT_URL.
|
|
23
|
+
#
|
|
24
|
+
# @attr [String] bulk_api_root_url
|
|
25
|
+
# The Zerobounce bulk API root URL.
|
|
26
|
+
#
|
|
20
27
|
# @attr [Array<Symbol>] valid_statues
|
|
21
28
|
# The statuses that are considered valid by {Response#valid?}.
|
|
22
29
|
class Configuration
|
|
23
|
-
attr_accessor :
|
|
24
|
-
attr_accessor :headers
|
|
25
|
-
attr_accessor :apikey
|
|
26
|
-
attr_accessor :valid_statuses
|
|
27
|
-
attr_accessor :mock
|
|
30
|
+
attr_accessor :headers, :apikey, :api_root_url, :bulk_api_root_url, :valid_statuses, :mock
|
|
28
31
|
|
|
29
|
-
def initialize(mock=false)
|
|
30
|
-
|
|
31
|
-
self.host = 'https://api.zerobounce.net'
|
|
32
|
+
def initialize(mock = false)
|
|
33
|
+
File.file?('.env') ? Dotenv.load('.env') : Dotenv.load
|
|
32
34
|
self.apikey = ENV['ZEROBOUNCE_API_KEY']
|
|
33
|
-
self.
|
|
35
|
+
self.api_root_url = ENV['ZEROBOUNCE_API_URL'] || ApiUrls::DEFAULT_URL
|
|
36
|
+
self.bulk_api_root_url = ENV['ZEROBOUNCE_BULK_API_URL'] || ApiUrls::BULK_DEFAULT_URL
|
|
37
|
+
self.valid_statuses = %i[valid catch_all accept_all]
|
|
34
38
|
self.headers = { user_agent: "ZerobounceRubyGem/#{Zerobounce::VERSION}" }
|
|
35
39
|
self.mock = mock
|
|
36
40
|
end
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
require 'stringio'
|
|
5
|
+
|
|
6
|
+
module Zerobounce
|
|
7
|
+
# Bulk getfile response handling (v2): HTTP errors, JSON error bodies (including HTTP 200).
|
|
8
|
+
module GetFileHelper
|
|
9
|
+
module_function
|
|
10
|
+
|
|
11
|
+
# @param [String] body
|
|
12
|
+
# @return [Boolean]
|
|
13
|
+
def json_indicates_error?(body)
|
|
14
|
+
t = body.to_s.lstrip
|
|
15
|
+
return false if t.empty? || t[0] != '{'
|
|
16
|
+
|
|
17
|
+
o = JSON.parse(t)
|
|
18
|
+
return false unless o.is_a?(Hash)
|
|
19
|
+
|
|
20
|
+
if o.key?('success')
|
|
21
|
+
s = o['success']
|
|
22
|
+
return true if s == false || s == 'False' || s == 'false'
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
%w[message error error_message].each do |k|
|
|
26
|
+
v = o[k]
|
|
27
|
+
next if v.nil?
|
|
28
|
+
|
|
29
|
+
return true if v.is_a?(String) && !v.strip.empty?
|
|
30
|
+
return true if v.is_a?(Array) && !v.empty?
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
o.key?('success')
|
|
34
|
+
rescue JSON::ParserError
|
|
35
|
+
false
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# @param [String] body
|
|
39
|
+
# @return [String]
|
|
40
|
+
def format_error_message(body)
|
|
41
|
+
o = JSON.parse(body.to_s)
|
|
42
|
+
return body.to_s unless o.is_a?(Hash)
|
|
43
|
+
|
|
44
|
+
%w[message error error_message].each do |k|
|
|
45
|
+
v = o[k]
|
|
46
|
+
next if v.nil?
|
|
47
|
+
return v.strip if v.is_a?(String) && !v.strip.empty?
|
|
48
|
+
return v[0].strip if v.is_a?(Array) && v[0].is_a?(String)
|
|
49
|
+
end
|
|
50
|
+
body.to_s
|
|
51
|
+
rescue JSON::ParserError
|
|
52
|
+
body.to_s.empty? ? 'Invalid getfile response' : body.to_s
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# @param [String] body
|
|
56
|
+
# @param [String] content_type
|
|
57
|
+
# @return [Boolean]
|
|
58
|
+
def should_treat_as_error?(body, content_type)
|
|
59
|
+
ct = content_type.to_s.downcase
|
|
60
|
+
return true if ct.include?('application/json')
|
|
61
|
+
|
|
62
|
+
json_indicates_error?(body)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# @param [RestClient::Response] response
|
|
66
|
+
# @return [String] CSV/text body on success
|
|
67
|
+
def process_getfile_response(response)
|
|
68
|
+
code = response.code.to_i
|
|
69
|
+
body_str = response.body.to_s
|
|
70
|
+
ct = (response.headers[:content_type] || response.headers['Content-Type'] || '').to_s
|
|
71
|
+
|
|
72
|
+
if code > 299
|
|
73
|
+
msg = if body_str.lstrip.start_with?('{')
|
|
74
|
+
format_error_message(body_str)
|
|
75
|
+
else
|
|
76
|
+
body_str.empty? ? "HTTP #{code}" : body_str
|
|
77
|
+
end
|
|
78
|
+
raise msg
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
if should_treat_as_error?(body_str, ct)
|
|
82
|
+
raise format_error_message(body_str)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
io = StringIO.new(response.body)
|
|
86
|
+
io.set_encoding_by_bom
|
|
87
|
+
io.string
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Zerobounce
|
|
4
|
+
# Optional query parameters for bulk getfile.
|
|
5
|
+
# +activity_data+ applies to validation +validate_file_get+ only; scoring getfile does not send it.
|
|
6
|
+
class GetFileOptions
|
|
7
|
+
attr_accessor :download_type, :activity_data
|
|
8
|
+
|
|
9
|
+
def initialize(download_type: nil, activity_data: nil)
|
|
10
|
+
@download_type = download_type
|
|
11
|
+
@activity_data = activity_data
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'zerobounce/base_request'
|
|
4
|
+
require 'zerobounce/get_file_helper'
|
|
4
5
|
|
|
5
6
|
module Zerobounce
|
|
6
7
|
|
|
@@ -8,7 +9,7 @@ module Zerobounce
|
|
|
8
9
|
class MockRequest < BaseRequest
|
|
9
10
|
|
|
10
11
|
def self.get(path, params, content_type='application/json')
|
|
11
|
-
response = self._get(Zerobounce
|
|
12
|
+
response = self._get(Zerobounce.configuration.api_root_url, path, params, content_type)
|
|
12
13
|
if response.headers[:content_type] == 'application/json'
|
|
13
14
|
response_body = response.body
|
|
14
15
|
response_body_json = JSON.parse(response_body)
|
|
@@ -25,7 +26,7 @@ module Zerobounce
|
|
|
25
26
|
end
|
|
26
27
|
|
|
27
28
|
def self.bulk_get(path, params, content_type='application/json')
|
|
28
|
-
response = self._get(Zerobounce
|
|
29
|
+
response = self._get(Zerobounce.configuration.bulk_api_root_url, path, params, content_type)
|
|
29
30
|
if response.headers[:content_type] == 'application/json'
|
|
30
31
|
response_body = response.body
|
|
31
32
|
response_body_json = JSON.parse(response_body)
|
|
@@ -41,8 +42,30 @@ module Zerobounce
|
|
|
41
42
|
end
|
|
42
43
|
end
|
|
43
44
|
|
|
45
|
+
def self.bulk_getfile(path, params)
|
|
46
|
+
response = self._get(Zerobounce.configuration.bulk_api_root_url, path, params, 'application/json')
|
|
47
|
+
GetFileHelper.process_getfile_response(response)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def self.post(path, params, content_type='application/json', filepath=nil)
|
|
51
|
+
response = self._post(Zerobounce.configuration.api_root_url, path, params, \
|
|
52
|
+
content_type, filepath)
|
|
53
|
+
if response.headers[:content_type] == 'application/json'
|
|
54
|
+
response_body = response.body
|
|
55
|
+
response_body_json = JSON.parse(response_body)
|
|
56
|
+
|
|
57
|
+
raise (response_body_json['error']) if response_body_json.key?('error')
|
|
58
|
+
raise (response_body_json['errors'][0]['error']) \
|
|
59
|
+
if response_body_json.key?('errors') and \
|
|
60
|
+
response_body_json['errors'].length > 0
|
|
61
|
+
|
|
62
|
+
return response_body_json
|
|
63
|
+
end
|
|
64
|
+
return response.body
|
|
65
|
+
end
|
|
66
|
+
|
|
44
67
|
def self.bulk_post(path, params, content_type='application/json', filepath=nil)
|
|
45
|
-
response = self._post(Zerobounce
|
|
68
|
+
response = self._post(Zerobounce.configuration.bulk_api_root_url, path, params, \
|
|
46
69
|
content_type, filepath)
|
|
47
70
|
if response.headers[:content_type] == 'application/json'
|
|
48
71
|
response_body = response.body
|
data/lib/zerobounce/request.rb
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require 'stringio'
|
|
4
4
|
require 'zerobounce/base_request'
|
|
5
|
+
require 'zerobounce/get_file_helper'
|
|
5
6
|
|
|
6
7
|
module Zerobounce
|
|
7
8
|
|
|
@@ -9,7 +10,7 @@ module Zerobounce
|
|
|
9
10
|
class Request < BaseRequest
|
|
10
11
|
|
|
11
12
|
def self.get(path, params, content_type='application/json')
|
|
12
|
-
response = self._get(Zerobounce
|
|
13
|
+
response = self._get(Zerobounce.configuration.api_root_url, path, params, content_type)
|
|
13
14
|
if response.headers[:content_type] == 'application/json'
|
|
14
15
|
response_body = response.body
|
|
15
16
|
response_body_json = JSON.parse(response_body)
|
|
@@ -29,7 +30,7 @@ module Zerobounce
|
|
|
29
30
|
end
|
|
30
31
|
|
|
31
32
|
def self.bulk_get(path, params, content_type='application/json')
|
|
32
|
-
response = self._get(Zerobounce
|
|
33
|
+
response = self._get(Zerobounce.configuration.bulk_api_root_url, path, params, content_type)
|
|
33
34
|
if response.headers[:content_type] == 'application/json'
|
|
34
35
|
response_body = response.body
|
|
35
36
|
response_body_json = JSON.parse(response_body)
|
|
@@ -47,8 +48,31 @@ module Zerobounce
|
|
|
47
48
|
end
|
|
48
49
|
end
|
|
49
50
|
|
|
51
|
+
# Bulk getfile only: treats non-2xx and JSON error payloads (including HTTP 200) as failures.
|
|
52
|
+
def self.bulk_getfile(path, params)
|
|
53
|
+
response = self._get(Zerobounce.configuration.bulk_api_root_url, path, params, 'application/json')
|
|
54
|
+
GetFileHelper.process_getfile_response(response)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def self.post(path, params, content_type='application/json', filepath=nil)
|
|
58
|
+
response = self._post(Zerobounce.configuration.api_root_url, path, params, \
|
|
59
|
+
content_type, filepath)
|
|
60
|
+
if response.headers[:content_type] == 'application/json'
|
|
61
|
+
response_body = response.body
|
|
62
|
+
response_body_json = JSON.parse(response_body)
|
|
63
|
+
|
|
64
|
+
raise (response_body_json['error']) if response_body_json.key?('error')
|
|
65
|
+
raise (response_body_json['errors'][0]['error']) \
|
|
66
|
+
if response_body_json.key?('errors') and \
|
|
67
|
+
response_body_json['errors'].length > 0
|
|
68
|
+
|
|
69
|
+
return response_body_json
|
|
70
|
+
end
|
|
71
|
+
return response.body
|
|
72
|
+
end
|
|
73
|
+
|
|
50
74
|
def self.bulk_post(path, params, content_type='application/json', filepath=nil)
|
|
51
|
-
response = self._post(Zerobounce
|
|
75
|
+
response = self._post(Zerobounce.configuration.bulk_api_root_url, path, params, \
|
|
52
76
|
content_type, filepath)
|
|
53
77
|
if response.headers[:content_type] == 'application/json'
|
|
54
78
|
response_body = response.body
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Zerobounce
|
|
4
|
+
# Validation status values returned by the API (validate, validate_batch).
|
|
5
|
+
# Use for comparison: response['status'] == Zerobounce::ValidateStatus::VALID
|
|
6
|
+
# Unknown/future API values are not listed; compare against response['status'] as string.
|
|
7
|
+
module ValidateStatus
|
|
8
|
+
NONE = ''
|
|
9
|
+
VALID = 'valid'
|
|
10
|
+
INVALID = 'invalid'
|
|
11
|
+
CATCH_ALL = 'catch-all'
|
|
12
|
+
UNKNOWN = 'unknown'
|
|
13
|
+
SPAMTRAP = 'spamtrap'
|
|
14
|
+
ABUSE = 'abuse'
|
|
15
|
+
DO_NOT_MAIL = 'do_not_mail'
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Zerobounce
|
|
4
|
+
# Validation sub-status values returned by the API (validate, validate_batch).
|
|
5
|
+
# Use for comparison: response['sub_status'] == Zerobounce::ValidateSubStatus::ACCEPT_ALL
|
|
6
|
+
# Unknown/future API values are not listed; compare against response['sub_status'] as string.
|
|
7
|
+
module ValidateSubStatus
|
|
8
|
+
NONE = ''
|
|
9
|
+
ANTISPAM_SYSTEM = 'antispam_system'
|
|
10
|
+
GREYLISTED = 'greylisted'
|
|
11
|
+
MAIL_SERVER_TEMPORARY_ERROR = 'mail_server_temporary_error'
|
|
12
|
+
FORCIBLE_DISCONNECT = 'forcible_disconnect'
|
|
13
|
+
MAIL_SERVER_DID_NOT_RESPOND = 'mail_server_did_not_respond'
|
|
14
|
+
TIMEOUT_EXCEEDED = 'timeout_exceeded'
|
|
15
|
+
FAILED_SMTP_CONNECTION = 'failed_smtp_connection'
|
|
16
|
+
MAILBOX_QUOTA_EXCEEDED = 'mailbox_quota_exceeded'
|
|
17
|
+
EXCEPTION_OCCURRED = 'exception_occurred'
|
|
18
|
+
POSSIBLE_TRAP = 'possible_trap'
|
|
19
|
+
ROLE_BASED = 'role_based'
|
|
20
|
+
GLOBAL_SUPPRESSION = 'global_suppression'
|
|
21
|
+
MAILBOX_NOT_FOUND = 'mailbox_not_found'
|
|
22
|
+
NO_DNS_ENTRIES = 'no_dns_entries'
|
|
23
|
+
FAILED_SYNTAX_CHECK = 'failed_syntax_check'
|
|
24
|
+
POSSIBLE_TYPO = 'possible_typo'
|
|
25
|
+
UNROUTABLE_IP_ADDRESS = 'unroutable_ip_address'
|
|
26
|
+
LEADING_PERIOD_REMOVED = 'leading_period_removed'
|
|
27
|
+
DOES_NOT_ACCEPT_MAIL = 'does_not_accept_mail'
|
|
28
|
+
ALIAS_ADDRESS = 'alias_address'
|
|
29
|
+
ROLE_BASED_CATCH_ALL = 'role_based_catch_all'
|
|
30
|
+
DISPOSABLE = 'disposable'
|
|
31
|
+
TOXIC = 'toxic'
|
|
32
|
+
ALTERNATE = 'alternate'
|
|
33
|
+
MX_FORWARD = 'mx_forward'
|
|
34
|
+
BLOCKED = 'blocked'
|
|
35
|
+
ALLOWED = 'allowed'
|
|
36
|
+
ACCEPT_ALL = 'accept_all'
|
|
37
|
+
ROLE_BASED_ACCEPT_ALL = 'role_based_accept_all'
|
|
38
|
+
GOLD = 'gold'
|
|
39
|
+
end
|
|
40
|
+
end
|
data/lib/zerobounce/version.rb
CHANGED