emailhunter 1.0.0 → 2.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 +4 -4
- data/Gemfile +2 -0
- data/README.md +276 -62
- data/Rakefile +2 -0
- data/bin/console +1 -0
- data/emailhunter.gemspec +21 -7
- data/lib/email_hunter/account.rb +16 -7
- data/lib/email_hunter/api.rb +103 -11
- data/lib/email_hunter/campaigns.rb +139 -0
- data/lib/email_hunter/combined_enrichment.rb +54 -0
- data/lib/email_hunter/company.rb +61 -0
- data/lib/email_hunter/company_enrichment.rb +61 -0
- data/lib/email_hunter/count.rb +17 -9
- data/lib/email_hunter/discover.rb +80 -0
- data/lib/email_hunter/exist.rb +14 -8
- data/lib/email_hunter/finder.rb +19 -5
- data/lib/email_hunter/lead_enrichment.rb +69 -0
- data/lib/email_hunter/leads.rb +129 -0
- data/lib/email_hunter/people.rb +61 -0
- data/lib/email_hunter/search.rb +24 -10
- data/lib/email_hunter/verify.rb +15 -10
- data/lib/email_hunter/version.rb +1 -1
- data/lib/emailhunter.rb +2 -0
- metadata +20 -9
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'faraday'
|
|
4
|
+
require 'json'
|
|
5
|
+
require 'ostruct'
|
|
6
|
+
|
|
7
|
+
module EmailHunter
|
|
8
|
+
class Campaigns
|
|
9
|
+
API_URL = 'https://api.hunter.io/v2/campaigns'
|
|
10
|
+
|
|
11
|
+
attr_reader :key, :params, :action, :campaign_id, :data
|
|
12
|
+
|
|
13
|
+
def initialize(key, action: :list, campaign_id: nil, data: nil, params: {})
|
|
14
|
+
@key = key
|
|
15
|
+
@action = action
|
|
16
|
+
@campaign_id = campaign_id
|
|
17
|
+
@data = data
|
|
18
|
+
@params = params
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def hunt
|
|
22
|
+
case action
|
|
23
|
+
when :list
|
|
24
|
+
list_campaigns
|
|
25
|
+
when :recipients
|
|
26
|
+
list_recipients
|
|
27
|
+
when :add_recipient
|
|
28
|
+
add_recipient
|
|
29
|
+
when :delete_recipient
|
|
30
|
+
delete_recipient
|
|
31
|
+
else
|
|
32
|
+
raise ArgumentError, "Unknown action: #{action}"
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def list_campaigns
|
|
39
|
+
response_data = fetch_campaigns_data
|
|
40
|
+
return nil if response_data.empty?
|
|
41
|
+
|
|
42
|
+
# Convert to OpenStruct for easy access
|
|
43
|
+
result = OpenStruct.new(response_data)
|
|
44
|
+
|
|
45
|
+
# Convert campaigns array to OpenStruct objects if it exists
|
|
46
|
+
if result.data.is_a?(Hash) && result.data[:campaigns].is_a?(Array)
|
|
47
|
+
campaigns_array = result.data[:campaigns].map { |c| OpenStruct.new(c) }
|
|
48
|
+
result.data = OpenStruct.new(result.data.merge(campaigns: campaigns_array))
|
|
49
|
+
elsif result.data.is_a?(OpenStruct) && result.data.campaigns.is_a?(Array)
|
|
50
|
+
result.data.campaigns = result.data.campaigns.map { |c| OpenStruct.new(c) }
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
result
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def list_recipients
|
|
57
|
+
response_data = fetch_recipients_data
|
|
58
|
+
return nil if response_data.empty?
|
|
59
|
+
|
|
60
|
+
result = OpenStruct.new(response_data)
|
|
61
|
+
|
|
62
|
+
# Convert recipients array to OpenStruct objects if it exists
|
|
63
|
+
if result.data.is_a?(Hash) && result.data[:recipients].is_a?(Array)
|
|
64
|
+
recipients_array = result.data[:recipients].map { |r| OpenStruct.new(r) }
|
|
65
|
+
result.data = OpenStruct.new(result.data.merge(recipients: recipients_array))
|
|
66
|
+
elsif result.data.is_a?(OpenStruct) && result.data.recipients.is_a?(Array)
|
|
67
|
+
result.data.recipients = result.data.recipients.map { |r| OpenStruct.new(r) }
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
result
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def add_recipient
|
|
74
|
+
response_data = post_recipient_data
|
|
75
|
+
return nil if response_data.empty?
|
|
76
|
+
|
|
77
|
+
OpenStruct.new(response_data)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def delete_recipient
|
|
81
|
+
response = delete_recipient_data
|
|
82
|
+
response.success?
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def fetch_campaigns_data
|
|
86
|
+
connection = Faraday.new
|
|
87
|
+
request_params = {
|
|
88
|
+
api_key: key,
|
|
89
|
+
limit: params.fetch(:limit, 20),
|
|
90
|
+
offset: params.fetch(:offset, 0)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
response = connection.get(API_URL, request_params)
|
|
94
|
+
|
|
95
|
+
return {} unless response.success?
|
|
96
|
+
|
|
97
|
+
JSON.parse(response.body, symbolize_names: true)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def fetch_recipients_data
|
|
101
|
+
connection = Faraday.new
|
|
102
|
+
url = "#{API_URL}/#{campaign_id}/recipients"
|
|
103
|
+
request_params = {
|
|
104
|
+
api_key: key,
|
|
105
|
+
limit: params.fetch(:limit, 20),
|
|
106
|
+
offset: params.fetch(:offset, 0)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
response = connection.get(url, request_params)
|
|
110
|
+
|
|
111
|
+
return {} unless response.success?
|
|
112
|
+
|
|
113
|
+
JSON.parse(response.body, symbolize_names: true)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def post_recipient_data
|
|
117
|
+
connection = Faraday.new
|
|
118
|
+
url = "#{API_URL}/#{campaign_id}/recipients"
|
|
119
|
+
request_data = data.merge(api_key: key)
|
|
120
|
+
|
|
121
|
+
response = connection.post(url) do |req|
|
|
122
|
+
req.headers['Content-Type'] = 'application/json'
|
|
123
|
+
req.body = request_data.to_json
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
return {} unless response.success?
|
|
127
|
+
|
|
128
|
+
JSON.parse(response.body, symbolize_names: true)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def delete_recipient_data
|
|
132
|
+
connection = Faraday.new
|
|
133
|
+
email = data[:email]
|
|
134
|
+
url = "#{API_URL}/#{campaign_id}/recipients/#{email}"
|
|
135
|
+
|
|
136
|
+
connection.delete(url, api_key: key)
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'ostruct'
|
|
4
|
+
require_relative 'lead_enrichment'
|
|
5
|
+
require_relative 'company_enrichment'
|
|
6
|
+
|
|
7
|
+
module EmailHunter
|
|
8
|
+
class CombinedEnrichment
|
|
9
|
+
attr_reader :key, :email, :linkedin
|
|
10
|
+
|
|
11
|
+
def initialize(key, email: nil, linkedin: nil)
|
|
12
|
+
@key = key
|
|
13
|
+
@email = email
|
|
14
|
+
@linkedin = linkedin
|
|
15
|
+
|
|
16
|
+
raise ArgumentError, 'Either email or linkedin must be provided' if email.nil? && linkedin.nil?
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def hunt
|
|
20
|
+
# First get lead enrichment data
|
|
21
|
+
lead_data = LeadEnrichment.new(key, email: email, linkedin: linkedin).hunt
|
|
22
|
+
return nil if lead_data.nil?
|
|
23
|
+
|
|
24
|
+
# Extract domain from employment if available
|
|
25
|
+
domain = lead_data.data.employment&.domain
|
|
26
|
+
return lead_data if domain.nil?
|
|
27
|
+
|
|
28
|
+
# Get company enrichment data
|
|
29
|
+
company_data = CompanyEnrichment.new(domain, key).hunt
|
|
30
|
+
|
|
31
|
+
# Combine the data
|
|
32
|
+
OpenStruct.new(
|
|
33
|
+
lead: lead_data,
|
|
34
|
+
company: company_data,
|
|
35
|
+
meta: OpenStruct.new(
|
|
36
|
+
email: email,
|
|
37
|
+
linkedin: linkedin,
|
|
38
|
+
domain: domain
|
|
39
|
+
)
|
|
40
|
+
)
|
|
41
|
+
rescue StandardError => e
|
|
42
|
+
# If company enrichment fails, just return lead data
|
|
43
|
+
OpenStruct.new(
|
|
44
|
+
lead: lead_data,
|
|
45
|
+
company: nil,
|
|
46
|
+
meta: OpenStruct.new(
|
|
47
|
+
email: email,
|
|
48
|
+
linkedin: linkedin,
|
|
49
|
+
error: e.message
|
|
50
|
+
)
|
|
51
|
+
)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'faraday'
|
|
4
|
+
require 'json'
|
|
5
|
+
require 'ostruct'
|
|
6
|
+
|
|
7
|
+
module EmailHunter
|
|
8
|
+
class Company
|
|
9
|
+
API_URL = 'https://api.hunter.io/v2/companies/find'
|
|
10
|
+
|
|
11
|
+
attr_reader :domain, :key
|
|
12
|
+
|
|
13
|
+
def initialize(domain, key)
|
|
14
|
+
@domain = domain
|
|
15
|
+
@key = key
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def hunt
|
|
19
|
+
response_data = fetch_company_data
|
|
20
|
+
return nil if response_data.empty?
|
|
21
|
+
|
|
22
|
+
# Convert nested data structure to a Struct
|
|
23
|
+
data = response_data[:data]
|
|
24
|
+
meta = response_data[:meta]
|
|
25
|
+
|
|
26
|
+
result = OpenStruct.new(
|
|
27
|
+
data: OpenStruct.new(data),
|
|
28
|
+
meta: OpenStruct.new(meta)
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
# Recursively convert nested hashes to OpenStructs for deeper access
|
|
32
|
+
convert_hash_to_struct(result.data, data)
|
|
33
|
+
|
|
34
|
+
result
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def fetch_company_data
|
|
40
|
+
@fetch_company_data ||= begin
|
|
41
|
+
connection = Faraday.new
|
|
42
|
+
response = connection.get(API_URL, domain: domain, api_key: key)
|
|
43
|
+
|
|
44
|
+
return {} unless response.success?
|
|
45
|
+
|
|
46
|
+
JSON.parse(response.body, symbolize_names: true)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def convert_hash_to_struct(struct, hash)
|
|
51
|
+
hash.each do |key, value|
|
|
52
|
+
if value.is_a?(Hash)
|
|
53
|
+
struct[key] = OpenStruct.new(value)
|
|
54
|
+
convert_hash_to_struct(struct[key], value)
|
|
55
|
+
elsif value.is_a?(Array) && value.first.is_a?(Hash)
|
|
56
|
+
struct[key] = value.map { |item| OpenStruct.new(item) }
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'faraday'
|
|
4
|
+
require 'json'
|
|
5
|
+
require 'ostruct'
|
|
6
|
+
|
|
7
|
+
module EmailHunter
|
|
8
|
+
class CompanyEnrichment
|
|
9
|
+
API_URL = 'https://api.hunter.io/v2/companies/find'
|
|
10
|
+
|
|
11
|
+
attr_reader :domain, :key
|
|
12
|
+
|
|
13
|
+
def initialize(domain, key)
|
|
14
|
+
@domain = domain
|
|
15
|
+
@key = key
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def hunt
|
|
19
|
+
response_data = fetch_enrichment_data
|
|
20
|
+
return nil if response_data.empty?
|
|
21
|
+
|
|
22
|
+
# Convert nested data structure to OpenStruct
|
|
23
|
+
data = response_data[:data]
|
|
24
|
+
meta = response_data[:meta]
|
|
25
|
+
|
|
26
|
+
result = OpenStruct.new(
|
|
27
|
+
data: OpenStruct.new(data),
|
|
28
|
+
meta: OpenStruct.new(meta)
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
# Recursively convert nested hashes to OpenStructs for deeper access
|
|
32
|
+
convert_hash_to_struct(result.data, data)
|
|
33
|
+
|
|
34
|
+
result
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def fetch_enrichment_data
|
|
40
|
+
@fetch_enrichment_data ||= begin
|
|
41
|
+
connection = Faraday.new
|
|
42
|
+
response = connection.get(API_URL, domain: domain, api_key: key)
|
|
43
|
+
|
|
44
|
+
return {} unless response.success?
|
|
45
|
+
|
|
46
|
+
JSON.parse(response.body, symbolize_names: true)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def convert_hash_to_struct(struct, hash)
|
|
51
|
+
hash.each do |key, value|
|
|
52
|
+
if value.is_a?(Hash)
|
|
53
|
+
struct[key] = OpenStruct.new(value)
|
|
54
|
+
convert_hash_to_struct(struct[key], value)
|
|
55
|
+
elsif value.is_a?(Array) && value.first.is_a?(Hash)
|
|
56
|
+
struct[key] = value.map { |item| OpenStruct.new(item) }
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
data/lib/email_hunter/count.rb
CHANGED
|
@@ -1,27 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'faraday'
|
|
2
4
|
require 'json'
|
|
3
5
|
|
|
4
|
-
API_COUNT_URL = 'https://api.hunter.io/v2/email-count?'.freeze
|
|
5
|
-
|
|
6
6
|
module EmailHunter
|
|
7
7
|
class Count
|
|
8
|
-
|
|
8
|
+
API_URL = 'https://api.hunter.io/v2/email-count'
|
|
9
|
+
|
|
10
|
+
attr_reader :domain
|
|
9
11
|
|
|
10
12
|
def initialize(domain)
|
|
11
13
|
@domain = domain
|
|
12
14
|
end
|
|
13
15
|
|
|
14
16
|
def hunt
|
|
15
|
-
|
|
17
|
+
response_data = fetch_count_data
|
|
18
|
+
return nil if response_data.empty?
|
|
19
|
+
|
|
20
|
+
Struct.new(*response_data.keys).new(*response_data.values)
|
|
16
21
|
end
|
|
17
22
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def fetch_count_data
|
|
26
|
+
@fetch_count_data ||= begin
|
|
27
|
+
connection = Faraday.new
|
|
28
|
+
response = connection.get(API_URL, domain: domain)
|
|
21
29
|
|
|
22
|
-
return
|
|
30
|
+
return {} unless response.success?
|
|
23
31
|
|
|
24
|
-
JSON.parse(response.body,
|
|
32
|
+
JSON.parse(response.body, symbolize_names: true)
|
|
25
33
|
end
|
|
26
34
|
end
|
|
27
35
|
end
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'faraday'
|
|
4
|
+
require 'json'
|
|
5
|
+
|
|
6
|
+
module EmailHunter
|
|
7
|
+
class Discover
|
|
8
|
+
API_URL = 'https://api.hunter.io/v2/discover'
|
|
9
|
+
|
|
10
|
+
attr_reader :query, :key, :params
|
|
11
|
+
|
|
12
|
+
def initialize(query, key, params = {})
|
|
13
|
+
@query = query
|
|
14
|
+
@key = key
|
|
15
|
+
@params = params
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def hunt
|
|
19
|
+
response_data = fetch_discover_data
|
|
20
|
+
return nil if response_data.empty?
|
|
21
|
+
|
|
22
|
+
# Convert to OpenStruct for easy access
|
|
23
|
+
result = OpenStruct.new(response_data)
|
|
24
|
+
|
|
25
|
+
# Recursively convert nested hashes to OpenStructs
|
|
26
|
+
convert_hash_to_struct(result, response_data)
|
|
27
|
+
|
|
28
|
+
result
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def convert_hash_to_struct(struct, hash)
|
|
34
|
+
hash.each do |key, value|
|
|
35
|
+
if value.is_a?(Hash)
|
|
36
|
+
struct[key] = OpenStruct.new(value)
|
|
37
|
+
convert_hash_to_struct(struct[key], value)
|
|
38
|
+
elsif value.is_a?(Array) && value.first.is_a?(Hash)
|
|
39
|
+
struct[key] = value.map { |item| OpenStruct.new(item) }
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def limit
|
|
45
|
+
params.fetch(:limit, 100).to_i
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def offset
|
|
49
|
+
params.fetch(:offset, 0).to_i
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def filters
|
|
53
|
+
params.fetch(:filters, {})
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def fetch_discover_data
|
|
57
|
+
@fetch_discover_data ||= begin
|
|
58
|
+
connection = Faraday.new
|
|
59
|
+
request_data = {
|
|
60
|
+
query: query,
|
|
61
|
+
api_key: key,
|
|
62
|
+
limit: limit,
|
|
63
|
+
offset: offset
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
# Add filters if provided
|
|
67
|
+
request_data[:filters] = filters unless filters.empty?
|
|
68
|
+
|
|
69
|
+
response = connection.post(API_URL) do |req|
|
|
70
|
+
req.headers['Content-Type'] = 'application/json'
|
|
71
|
+
req.body = request_data.to_json
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
return {} unless response.success?
|
|
75
|
+
|
|
76
|
+
JSON.parse(response.body, symbolize_names: true)
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
data/lib/email_hunter/exist.rb
CHANGED
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
require 'faraday'
|
|
4
4
|
require 'json'
|
|
5
5
|
|
|
6
|
-
API_EXIST_URL = 'https://api.emailhunter.co/v1/exist?'
|
|
7
|
-
|
|
8
6
|
module EmailHunter
|
|
9
7
|
class Exist
|
|
10
|
-
|
|
8
|
+
API_URL = 'https://api.emailhunter.co/v1/exist'
|
|
9
|
+
|
|
10
|
+
attr_reader :email, :key
|
|
11
11
|
|
|
12
12
|
def initialize(email, key)
|
|
13
13
|
@email = email
|
|
@@ -15,16 +15,22 @@ module EmailHunter
|
|
|
15
15
|
end
|
|
16
16
|
|
|
17
17
|
def hunt
|
|
18
|
-
|
|
18
|
+
response_data = fetch_exist_data
|
|
19
|
+
return nil if response_data.empty?
|
|
20
|
+
|
|
21
|
+
Struct.new(*response_data.keys).new(*response_data.values)
|
|
19
22
|
end
|
|
20
23
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def fetch_exist_data
|
|
27
|
+
@fetch_exist_data ||= begin
|
|
28
|
+
connection = Faraday.new
|
|
29
|
+
response = connection.get(API_URL, email: email, api_key: key)
|
|
24
30
|
|
|
25
31
|
return {} unless response.success?
|
|
26
32
|
|
|
27
|
-
JSON.parse(response.body,
|
|
33
|
+
JSON.parse(response.body, symbolize_names: true)
|
|
28
34
|
end
|
|
29
35
|
end
|
|
30
36
|
end
|
data/lib/email_hunter/finder.rb
CHANGED
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
require 'faraday'
|
|
4
4
|
require 'json'
|
|
5
5
|
|
|
6
|
-
API_FINDER_URL = 'https://api.hunter.io/v2/email-finder?'
|
|
7
|
-
|
|
8
6
|
module EmailHunter
|
|
9
7
|
class Finder
|
|
8
|
+
API_FINDER_URL = 'https://api.hunter.io/v2/email-finder'
|
|
9
|
+
|
|
10
10
|
attr_reader :email, :score, :sources, :domain, :meta, :key, :first_name, :last_name
|
|
11
11
|
|
|
12
12
|
def initialize(domain, first_name, last_name, key)
|
|
@@ -22,12 +22,26 @@ module EmailHunter
|
|
|
22
22
|
|
|
23
23
|
def data
|
|
24
24
|
@data ||= begin
|
|
25
|
-
response =
|
|
25
|
+
response = fetch_finder_data
|
|
26
26
|
|
|
27
|
-
return
|
|
27
|
+
return {} unless response.success?
|
|
28
28
|
|
|
29
|
-
JSON.parse(response.body,
|
|
29
|
+
JSON.parse(response.body, symbolize_names: true)
|
|
30
30
|
end
|
|
31
31
|
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def fetch_finder_data
|
|
36
|
+
connection = Faraday.new
|
|
37
|
+
params = {
|
|
38
|
+
domain: domain,
|
|
39
|
+
first_name: first_name,
|
|
40
|
+
last_name: last_name,
|
|
41
|
+
api_key: key
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
connection.get(API_FINDER_URL, params)
|
|
45
|
+
end
|
|
32
46
|
end
|
|
33
47
|
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'faraday'
|
|
4
|
+
require 'json'
|
|
5
|
+
require 'ostruct'
|
|
6
|
+
|
|
7
|
+
module EmailHunter
|
|
8
|
+
class LeadEnrichment
|
|
9
|
+
API_URL = 'https://api.hunter.io/v2/people/find'
|
|
10
|
+
|
|
11
|
+
attr_reader :key, :email, :linkedin
|
|
12
|
+
|
|
13
|
+
def initialize(key, email: nil, linkedin: nil)
|
|
14
|
+
@key = key
|
|
15
|
+
@email = email
|
|
16
|
+
@linkedin = linkedin
|
|
17
|
+
|
|
18
|
+
raise ArgumentError, 'Either email or linkedin must be provided' if email.nil? && linkedin.nil?
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def hunt
|
|
22
|
+
response_data = fetch_enrichment_data
|
|
23
|
+
return nil if response_data.empty?
|
|
24
|
+
|
|
25
|
+
# Convert nested data structure to OpenStruct
|
|
26
|
+
data = response_data[:data]
|
|
27
|
+
meta = response_data[:meta]
|
|
28
|
+
|
|
29
|
+
result = OpenStruct.new(
|
|
30
|
+
data: OpenStruct.new(data),
|
|
31
|
+
meta: OpenStruct.new(meta)
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
# Recursively convert nested hashes to OpenStructs for deeper access
|
|
35
|
+
convert_hash_to_struct(result.data, data)
|
|
36
|
+
|
|
37
|
+
result
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def fetch_enrichment_data
|
|
43
|
+
@fetch_enrichment_data ||= begin
|
|
44
|
+
connection = Faraday.new
|
|
45
|
+
request_params = { api_key: key }
|
|
46
|
+
|
|
47
|
+
request_params[:email] = email if email
|
|
48
|
+
request_params[:linkedin_handle] = linkedin if linkedin
|
|
49
|
+
|
|
50
|
+
response = connection.get(API_URL, request_params)
|
|
51
|
+
|
|
52
|
+
return {} unless response.success?
|
|
53
|
+
|
|
54
|
+
JSON.parse(response.body, symbolize_names: true)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def convert_hash_to_struct(struct, hash)
|
|
59
|
+
hash.each do |key, value|
|
|
60
|
+
if value.is_a?(Hash)
|
|
61
|
+
struct[key] = OpenStruct.new(value)
|
|
62
|
+
convert_hash_to_struct(struct[key], value)
|
|
63
|
+
elsif value.is_a?(Array) && value.first.is_a?(Hash)
|
|
64
|
+
struct[key] = value.map { |item| OpenStruct.new(item) }
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|