email_inquire 0.9.0 → 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +10 -78
- data/CHANGELOG.md +17 -2
- data/Gemfile +3 -0
- data/README.md +71 -6
- data/data/country_code_tld/br.txt +72 -0
- data/data/country_code_tld/jp.txt +9 -0
- data/data/country_code_tld/uk.txt +15 -0
- data/data/{one_time_email_providers.txt → one_time_providers.txt} +0 -0
- data/lib/email_inquire.rb +6 -16
- data/lib/email_inquire/helper.rb +13 -0
- data/lib/email_inquire/inquirer.rb +35 -161
- data/lib/email_inquire/response.rb +17 -6
- data/lib/email_inquire/validator/base.rb +49 -0
- data/lib/email_inquire/validator/common_provider.rb +18 -0
- data/lib/email_inquire/validator/common_provider_mistake.rb +26 -0
- data/lib/email_inquire/validator/commonly_mistaken_domain.rb +27 -0
- data/lib/email_inquire/validator/commonly_mistaken_tld.rb +29 -0
- data/lib/email_inquire/validator/country_code_tld.rb +102 -0
- data/lib/email_inquire/validator/custom_invalid_domain.rb +15 -0
- data/lib/email_inquire/validator/custom_valid_domain.rb +15 -0
- data/lib/email_inquire/validator/email_format.rb +54 -0
- data/lib/email_inquire/validator/known_invalid_domain.rb +17 -0
- data/lib/email_inquire/validator/one_time_provider.rb +17 -0
- data/lib/email_inquire/validator/unique_domain_provider.rb +28 -0
- data/lib/email_inquire/version.rb +1 -1
- metadata +19 -7
- data/data/br_tld.txt +0 -72
- data/data/jp_tld.txt +0 -9
- data/data/uk_tld.txt +0 -15
- data/lib/email_inquire/email_validator.rb +0 -67
@@ -1,181 +1,55 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "
|
4
|
-
require "
|
3
|
+
require "email_inquire/helper"
|
4
|
+
require "email_inquire/response"
|
5
|
+
require "email_inquire/validator/common_provider"
|
6
|
+
require "email_inquire/validator/common_provider_mistake"
|
7
|
+
require "email_inquire/validator/commonly_mistaken_domain"
|
8
|
+
require "email_inquire/validator/commonly_mistaken_tld"
|
9
|
+
require "email_inquire/validator/country_code_tld"
|
10
|
+
require "email_inquire/validator/custom_invalid_domain"
|
11
|
+
require "email_inquire/validator/custom_valid_domain"
|
12
|
+
require "email_inquire/validator/email_format"
|
13
|
+
require "email_inquire/validator/known_invalid_domain"
|
14
|
+
require "email_inquire/validator/one_time_provider"
|
15
|
+
require "email_inquire/validator/unique_domain_provider"
|
5
16
|
|
6
17
|
module EmailInquire
|
7
18
|
class Inquirer
|
8
19
|
|
9
|
-
class << self
|
10
|
-
|
11
|
-
private
|
12
|
-
|
13
|
-
def load_data(filename)
|
14
|
-
data = File.read("#{__dir__}/../../data/#{filename}.txt")
|
15
|
-
lines = data.split("\n")
|
16
|
-
lines.reject! { |line| line[0] == "#" }
|
17
|
-
|
18
|
-
lines.to_set
|
19
|
-
end
|
20
|
-
|
21
|
-
end
|
22
|
-
|
23
20
|
def initialize(email)
|
24
|
-
@email = email
|
25
|
-
|
26
|
-
parse_email
|
27
|
-
end
|
28
|
-
|
29
|
-
attr_reader :domain, :email, :name
|
30
|
-
|
31
|
-
VALIDATORS = %i[
|
32
|
-
validate_custom_valid_domains
|
33
|
-
validate_common_domains
|
34
|
-
validate_one_time_providers
|
35
|
-
validate_known_invalid_domains
|
36
|
-
validate_custom_invalid_domains
|
37
|
-
validate_common_domain_mistakes
|
38
|
-
validate_cc_tld
|
39
|
-
validate_common_tld_mistakes
|
40
|
-
validate_domains_with_unique_tld
|
41
|
-
].freeze
|
42
|
-
|
43
|
-
def validate
|
44
|
-
email_validator = EmailValidator.new(email)
|
45
|
-
unless email_validator.valid?
|
46
|
-
response.invalid!
|
47
|
-
return response
|
48
|
-
end
|
49
|
-
|
50
|
-
VALIDATORS.each do |validator|
|
51
|
-
send(validator)
|
52
|
-
break if response.valid? || response.invalid?
|
53
|
-
end
|
54
|
-
|
55
|
-
# default
|
56
|
-
response.valid! unless response.status?
|
57
|
-
|
58
|
-
response
|
59
|
-
end
|
60
|
-
|
61
|
-
private
|
62
|
-
|
63
|
-
def parse_email
|
64
|
-
@name, @domain = email.split("@")
|
65
|
-
end
|
66
|
-
|
67
|
-
def response
|
68
|
-
@response ||=
|
69
|
-
Response.new.tap do |response|
|
70
|
-
response.email = email
|
71
|
-
end
|
72
|
-
end
|
73
|
-
|
74
|
-
COMMON_DOMAIN_MISTAKES = {
|
75
|
-
/google(?!mail)/ => "gmail.com",
|
76
|
-
/windows.*\.com/ => "live.com",
|
77
|
-
}.freeze
|
78
|
-
|
79
|
-
def validate_common_domain_mistakes
|
80
|
-
COMMON_DOMAIN_MISTAKES.each do |mistake, reference|
|
81
|
-
break if domain == reference # valid!
|
82
|
-
|
83
|
-
if mistake =~ domain
|
84
|
-
response.hint!(domain: reference)
|
85
|
-
break
|
86
|
-
end
|
87
|
-
end
|
21
|
+
@email = email&.downcase
|
88
22
|
end
|
89
23
|
|
90
|
-
|
91
|
-
|
92
|
-
def validate_common_domains
|
93
|
-
return response.valid! if COMMON_DOMAINS.include?(domain)
|
24
|
+
attr_reader :email
|
94
25
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
response.hint!(domain: reference)
|
99
|
-
break
|
100
|
-
end
|
101
|
-
end
|
102
|
-
end
|
26
|
+
VALIDATORS = [
|
27
|
+
# Format first
|
28
|
+
EmailInquire::Validator::EmailFormat,
|
103
29
|
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
".couk" => ".co.uk",
|
108
|
-
".com.com" => ".com",
|
109
|
-
}.freeze
|
30
|
+
# Custom overrides
|
31
|
+
EmailInquire::Validator::CustomValidDomain,
|
32
|
+
EmailInquire::Validator::CustomInvalidDomain,
|
110
33
|
|
111
|
-
|
112
|
-
|
113
|
-
break if !mistake.end_with?(reference) && domain.end_with?(reference)
|
34
|
+
# Always valid domains
|
35
|
+
EmailInquire::Validator::CommonProvider,
|
114
36
|
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
end
|
119
|
-
end
|
120
|
-
end
|
37
|
+
# Invalid domains
|
38
|
+
EmailInquire::Validator::KnownInvalidDomain,
|
39
|
+
EmailInquire::Validator::OneTimeProvider,
|
121
40
|
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
41
|
+
# Hints
|
42
|
+
EmailInquire::Validator::CommonProviderMistake,
|
43
|
+
EmailInquire::Validator::CommonlyMistakenDomain,
|
44
|
+
EmailInquire::Validator::CommonlyMistakenTld,
|
45
|
+
EmailInquire::Validator::CountryCodeTld,
|
46
|
+
EmailInquire::Validator::UniqueDomainProvider,
|
126
47
|
].freeze
|
127
48
|
|
128
|
-
def
|
129
|
-
|
130
|
-
next unless domain.end_with?(tld)
|
131
|
-
|
132
|
-
next if valid_tlds.any? do |reference|
|
133
|
-
domain.end_with?(reference)
|
134
|
-
end
|
135
|
-
|
136
|
-
_, com, tld_without_dot = sld.split(".")
|
137
|
-
|
138
|
-
new_domain = domain.dup
|
139
|
-
new_domain.gsub!(/\.[a-z]{2,#{com.length}}\.#{tld_without_dot}\z/, sld)
|
140
|
-
new_domain.gsub!(/(?<!\.)#{com}\.#{tld_without_dot}\z/, sld)
|
141
|
-
new_domain.gsub!(/(?<!\.#{com})\.#{tld_without_dot}\z/, sld)
|
142
|
-
response.hint!(domain: new_domain) if new_domain != domain
|
143
|
-
end
|
144
|
-
end
|
145
|
-
|
146
|
-
UNIQUE_TLD_DOMAINS = load_data("unique_domain_providers").freeze
|
147
|
-
|
148
|
-
def validate_domains_with_unique_tld
|
149
|
-
base, tld = domain.split(".")
|
150
|
-
|
151
|
-
UNIQUE_TLD_DOMAINS.each do |reference|
|
152
|
-
reference_base, reference_tld = reference.split(".")
|
153
|
-
|
154
|
-
if base == reference_base && tld != reference_tld
|
155
|
-
response.hint!(domain: reference)
|
156
|
-
break
|
157
|
-
end
|
158
|
-
end
|
159
|
-
end
|
160
|
-
|
161
|
-
ONE_TIME_EMAIL_PROVIDERS = load_data("one_time_email_providers").freeze
|
162
|
-
|
163
|
-
def validate_one_time_providers
|
164
|
-
response.invalid! if ONE_TIME_EMAIL_PROVIDERS.include?(domain)
|
165
|
-
end
|
166
|
-
|
167
|
-
KNOWN_INVALID_DOMAINS = load_data("known_invalid_domains").freeze
|
168
|
-
|
169
|
-
def validate_known_invalid_domains
|
170
|
-
response.invalid! if KNOWN_INVALID_DOMAINS.include?(domain)
|
171
|
-
end
|
172
|
-
|
173
|
-
def validate_custom_invalid_domains
|
174
|
-
response.invalid! if EmailInquire.custom_invalid_domains.include?(domain)
|
175
|
-
end
|
49
|
+
def validate
|
50
|
+
response = Helper.first_value(VALIDATORS) { |validator| validator.validate(email) }
|
176
51
|
|
177
|
-
|
178
|
-
response.valid! if EmailInquire.custom_valid_domains.include?(domain)
|
52
|
+
response || Response.new(email: email).valid!
|
179
53
|
end
|
180
54
|
|
181
55
|
end
|
@@ -3,25 +3,34 @@
|
|
3
3
|
module EmailInquire
|
4
4
|
class Response
|
5
5
|
|
6
|
-
|
6
|
+
attr_reader :email
|
7
|
+
attr_accessor :replacement, :status
|
7
8
|
|
8
|
-
def
|
9
|
+
def initialize(email:)
|
10
|
+
@email = email
|
11
|
+
end
|
12
|
+
|
13
|
+
def hint!(domain:)
|
9
14
|
self.status = :hint
|
10
15
|
|
11
16
|
old_name, _old_domain = email.split("@")
|
12
|
-
self.replacement = "#{old_name}@#{domain}"
|
17
|
+
self.replacement = "#{old_name}@#{domain}"
|
18
|
+
|
19
|
+
self
|
13
20
|
end
|
14
21
|
|
15
22
|
def hint?
|
16
|
-
status
|
23
|
+
status.equal?(:hint)
|
17
24
|
end
|
18
25
|
|
19
26
|
def invalid!
|
20
27
|
self.status = :invalid
|
28
|
+
|
29
|
+
self
|
21
30
|
end
|
22
31
|
|
23
32
|
def invalid?
|
24
|
-
status
|
33
|
+
status.equal?(:invalid)
|
25
34
|
end
|
26
35
|
|
27
36
|
def status?
|
@@ -30,10 +39,12 @@ module EmailInquire
|
|
30
39
|
|
31
40
|
def valid!
|
32
41
|
self.status = :valid
|
42
|
+
|
43
|
+
self
|
33
44
|
end
|
34
45
|
|
35
46
|
def valid?
|
36
|
-
status
|
47
|
+
status.equal?(:valid)
|
37
48
|
end
|
38
49
|
|
39
50
|
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "set"
|
4
|
+
require "email_inquire/response"
|
5
|
+
|
6
|
+
module EmailInquire
|
7
|
+
module Validator
|
8
|
+
class Base
|
9
|
+
|
10
|
+
class << self
|
11
|
+
|
12
|
+
def validate(email)
|
13
|
+
new(email).validate
|
14
|
+
end
|
15
|
+
|
16
|
+
private :new
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def load_data(filename)
|
21
|
+
data = File.read("#{__dir__}/../../../data/#{filename}.txt")
|
22
|
+
lines = data.split("\n")
|
23
|
+
lines.reject! { |line| line[0] == "#" }
|
24
|
+
|
25
|
+
lines.to_set
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
def initialize(email)
|
31
|
+
@email = email
|
32
|
+
@name, @domain = email&.split("@", 2)
|
33
|
+
end
|
34
|
+
|
35
|
+
attr_reader :domain, :email, :name
|
36
|
+
|
37
|
+
def validate
|
38
|
+
raise NotImplementedError
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def response
|
44
|
+
@response ||= Response.new(email: email)
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "damerau-levenshtein"
|
4
|
+
require "email_inquire/validator/base"
|
5
|
+
|
6
|
+
module EmailInquire
|
7
|
+
module Validator
|
8
|
+
class CommonProvider < Base
|
9
|
+
|
10
|
+
DOMAINS = load_data("common_providers").freeze
|
11
|
+
|
12
|
+
def validate
|
13
|
+
response.valid! if DOMAINS.include?(domain)
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "damerau-levenshtein"
|
4
|
+
require "email_inquire/validator/base"
|
5
|
+
require "email_inquire/validator/common_provider"
|
6
|
+
|
7
|
+
module EmailInquire
|
8
|
+
module Validator
|
9
|
+
class CommonProviderMistake < Base
|
10
|
+
|
11
|
+
def validate
|
12
|
+
return if CommonProvider::DOMAINS.include?(domain)
|
13
|
+
|
14
|
+
replacement_domain =
|
15
|
+
CommonProvider::DOMAINS.find do |reference|
|
16
|
+
distance = DamerauLevenshtein.distance(domain, reference)
|
17
|
+
|
18
|
+
distance.equal?(1)
|
19
|
+
end
|
20
|
+
|
21
|
+
response.hint!(domain: replacement_domain) if replacement_domain
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "email_inquire/validator/base"
|
4
|
+
|
5
|
+
module EmailInquire
|
6
|
+
module Validator
|
7
|
+
class CommonlyMistakenDomain < Base
|
8
|
+
|
9
|
+
MISTAKES = {
|
10
|
+
/google(?!mail)/ => "gmail.com",
|
11
|
+
/windows.*\.com/ => "live.com",
|
12
|
+
}.freeze
|
13
|
+
|
14
|
+
def validate
|
15
|
+
return response.valid! if MISTAKES.value?(domain)
|
16
|
+
|
17
|
+
_mistake, reference =
|
18
|
+
MISTAKES.find do |mistake, _reference|
|
19
|
+
mistake =~ domain
|
20
|
+
end
|
21
|
+
|
22
|
+
response.hint!(domain: reference) if reference
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "email_inquire/validator/base"
|
4
|
+
|
5
|
+
module EmailInquire
|
6
|
+
module Validator
|
7
|
+
class CommonlyMistakenTld < Base
|
8
|
+
|
9
|
+
MISTAKES = {
|
10
|
+
".combr" => ".com.br",
|
11
|
+
".cojp" => ".co.jp",
|
12
|
+
".couk" => ".co.uk",
|
13
|
+
".com.com" => ".com",
|
14
|
+
}.freeze
|
15
|
+
|
16
|
+
def validate
|
17
|
+
mistake, reference =
|
18
|
+
MISTAKES.find do |mistake, reference|
|
19
|
+
next if !mistake.end_with?(reference) && domain.end_with?(reference)
|
20
|
+
|
21
|
+
domain.end_with?(mistake)
|
22
|
+
end
|
23
|
+
|
24
|
+
response.hint!(domain: domain.sub(/#{mistake}\z/, reference)) if reference
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "email_inquire/helper"
|
4
|
+
require "email_inquire/validator/base"
|
5
|
+
require "email_inquire/validator/common_provider"
|
6
|
+
|
7
|
+
module EmailInquire
|
8
|
+
module Validator
|
9
|
+
class CountryCodeTld < Base
|
10
|
+
|
11
|
+
COUNTRY_CODE_TLDS = [
|
12
|
+
# TLD, generic com, all generic, registration with TLD only is possible
|
13
|
+
["jp", "co", load_data("country_code_tld/jp").freeze, true].freeze,
|
14
|
+
["uk", "co", load_data("country_code_tld/uk").freeze, true].freeze,
|
15
|
+
["br", "com", load_data("country_code_tld/br").freeze, true].freeze,
|
16
|
+
].freeze
|
17
|
+
|
18
|
+
def initialize(email)
|
19
|
+
super(email)
|
20
|
+
|
21
|
+
*@rest, @sld, @tld = domain.split(".")
|
22
|
+
end
|
23
|
+
|
24
|
+
def validate
|
25
|
+
Helper.first_value(
|
26
|
+
COUNTRY_CODE_TLDS
|
27
|
+
) do |cctld, generic_com, all_generics, registration_with_cctld|
|
28
|
+
validate_cctld(cctld, generic_com, all_generics, registration_with_cctld)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
attr_reader :rest, :sld, :tld
|
35
|
+
|
36
|
+
def hint_for_approx_cctld(cctld, all_generics)
|
37
|
+
matching_generic = all_generics.find { |generic| tld.eql?("#{generic}#{cctld}") }
|
38
|
+
return unless matching_generic
|
39
|
+
|
40
|
+
replacement = [*rest, sld, matching_generic, cctld].join(".")
|
41
|
+
response.hint!(domain: replacement)
|
42
|
+
end
|
43
|
+
|
44
|
+
def hint_for_common_provider(cctld, generic_com)
|
45
|
+
provider_domain = [
|
46
|
+
sld,
|
47
|
+
generic_com,
|
48
|
+
cctld,
|
49
|
+
].join(".")
|
50
|
+
|
51
|
+
return unless CommonProvider::DOMAINS.include?(provider_domain)
|
52
|
+
|
53
|
+
response.hint!(domain: provider_domain)
|
54
|
+
end
|
55
|
+
|
56
|
+
def hint_for_generic_com(cctld, generic_com)
|
57
|
+
replacement = [
|
58
|
+
*rest,
|
59
|
+
(sld if sld.length > 2),
|
60
|
+
generic_com,
|
61
|
+
cctld,
|
62
|
+
].compact.join(".")
|
63
|
+
|
64
|
+
response.hint!(domain: replacement)
|
65
|
+
end
|
66
|
+
|
67
|
+
def hint_for_generic_at_end_of_sld(cctld, all_generics)
|
68
|
+
generic_at_end_of_sld = all_generics.find { |generic| sld.end_with?(generic) }
|
69
|
+
return unless generic_at_end_of_sld
|
70
|
+
|
71
|
+
replacement = [
|
72
|
+
*rest,
|
73
|
+
sld.sub(/#{generic_at_end_of_sld}\z/, ""),
|
74
|
+
generic_at_end_of_sld,
|
75
|
+
cctld,
|
76
|
+
].join(".")
|
77
|
+
|
78
|
+
response.hint!(domain: replacement)
|
79
|
+
end
|
80
|
+
|
81
|
+
def validate_cctld(cctld, generic_com, all_generics, registration_with_cctld)
|
82
|
+
return hint_for_approx_cctld(cctld, all_generics) unless tld.eql?(cctld)
|
83
|
+
|
84
|
+
if all_generics.include?(sld)
|
85
|
+
return response.invalid! if rest.empty?
|
86
|
+
|
87
|
+
return
|
88
|
+
end
|
89
|
+
|
90
|
+
(
|
91
|
+
hint_for_generic_at_end_of_sld(cctld, all_generics) ||
|
92
|
+
hint_for_common_provider(cctld, generic_com)
|
93
|
+
).tap do |hint|
|
94
|
+
return hint if hint
|
95
|
+
end
|
96
|
+
|
97
|
+
hint_for_generic_com(cctld, generic_com) if sld.length <= 2 || !registration_with_cctld
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|