emailhunter 1.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b4783ca3e6457c776875732582c772c3d1d112934384922f9816f22a401107b0
4
- data.tar.gz: 2a12c9cb64c3ad77b9e902d0113ddc68b4c56c04c0530aa21e72b02523f9f58b
3
+ metadata.gz: 2006e973eb996d6b4285c772c1af115e0d96091c403fff927381233f4ebeed94
4
+ data.tar.gz: 51433f5603cbb512baedb0342eb83f7655505ae4a4ee5f4deac18d317d8861ae
5
5
  SHA512:
6
- metadata.gz: 5e2b5c05259327cd62ceef07587518f755e57d4d577fff2bd4f38f1afba24b920d7d8ab0528ac27d86c80d53e4bdd3984a0a7d992a2d26d7f30141945b06bc32
7
- data.tar.gz: 1ab01074fbe447ad24b2d39ce89bdf444b0b2cbdf280a615b94a17642bc350147135468d2651a7558fb00f6dc2cf271ca946e0291e3f5529d500d12bbc3f71d2
6
+ metadata.gz: 71a8eebcf7e1c0452a08d0d8ef4c79d980b1d48dbe0d898dab096b8012267dc2d7e7d4ade75ea861635dfa78690522b409993a60f065d3901299f85de161715f
7
+ data.tar.gz: 27c55a270224f1a9bc513c54903d0e5c16598bc939270dd331422dea13487c2fb733f5cb90f47efd83dd4628180730cf3baf9b1f90ff6ecff9bc1d2871c5103f
data/Gemfile CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  source 'https://rubygems.org'
2
4
 
3
5
  # Specify your gem's dependencies in emailhunter.gemspec
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # EmailHunter
2
2
 
3
- A lightweight Ruby wrapper around Hunter (formerly Email Hunter) API, providing direct access to email search, verification, and company insights.
3
+ A lightweight Ruby wrapper around [Hunter.io](https://hunter.io/) (formerly Email Hunter) API, providing direct access to email search, verification, and company insights.
4
4
 
5
5
  ## Installation
6
6
 
@@ -33,7 +33,20 @@ Your API key can be generated in your [Hunter dashboard](https://hunter.io).
33
33
 
34
34
  ## Features
35
35
 
36
+ **Core APIs:**
37
+
38
+ 1. **Domain Search API** - Retrieve all email addresses associated with a domain
39
+ 2. **Email Verification API** - Check the deliverability of an email address
40
+ 3. **Email Finder API** - Find the most likely email using name and domain
41
+ 4. **Count API** - Get the number of email addresses for a domain (FREE)
42
+ 5. **Account Information API** - Retrieve details about your Hunter account
43
+ 6. **Company Information API** - Get company details using a domain name
44
+ 7. **People Search API** - Find key individuals associated with a company
45
+
46
+ ---
47
+
36
48
  ### 1. Domain Search API
49
+
37
50
  Retrieve all email addresses associated with a given domain.
38
51
 
39
52
  ```ruby
@@ -41,6 +54,7 @@ result = email_hunter.search('stripe.com')
41
54
  ```
42
55
 
43
56
  #### Response Fields:
57
+
44
58
  ```ruby
45
59
  result.fetch(:meta)
46
60
  result.fetch(:webmail)
@@ -50,6 +64,7 @@ result.fetch(:domain)
50
64
  ```
51
65
 
52
66
  ### 2. Email Verification API
67
+
53
68
  Check the deliverability of an email address.
54
69
 
55
70
  ```ruby
@@ -57,6 +72,7 @@ result = email_hunter.verify('bonjour@firmapi.com')
57
72
  ```
58
73
 
59
74
  #### Response Fields:
75
+
60
76
  ```ruby
61
77
  result.fetch(:result)
62
78
  result.fetch(:score)
@@ -72,6 +88,7 @@ result.fetch(:meta)
72
88
  ```
73
89
 
74
90
  ### 3. Email Finder API
91
+
75
92
  Guess the most likely email of a person using their first name, last name, and domain.
76
93
 
77
94
  ```ruby
@@ -79,6 +96,7 @@ result = email_hunter.finder('gmail.com', 'Davide', 'Santangelo')
79
96
  ```
80
97
 
81
98
  #### Response Fields:
99
+
82
100
  ```ruby
83
101
  result.fetch(:email)
84
102
  result.fetch(:score)
@@ -88,6 +106,7 @@ result.fetch(:meta)
88
106
  ```
89
107
 
90
108
  ### 4. Count API
109
+
91
110
  Retrieve the number of email addresses associated with a domain (FREE API call).
92
111
 
93
112
  ```ruby
@@ -95,12 +114,14 @@ result = email_hunter.count('gmail.com')
95
114
  ```
96
115
 
97
116
  #### Response Fields:
117
+
98
118
  ```ruby
99
119
  result.fetch(:data)
100
120
  result.fetch(:meta)
101
121
  ```
102
122
 
103
123
  ### 5. Company Information API (New Feature)
124
+
104
125
  Retrieve company details using a domain name.
105
126
 
106
127
  ```ruby
@@ -108,6 +129,7 @@ result = email_hunter.company('stripe.com')
108
129
  ```
109
130
 
110
131
  #### Response Fields:
132
+
111
133
  ```ruby
112
134
  result.fetch(:name)
113
135
  result.fetch(:industry)
@@ -117,6 +139,7 @@ result.fetch(:meta)
117
139
  ```
118
140
 
119
141
  ### 6. People Search API (New Feature)
142
+
120
143
  Retrieve key individuals associated with a company based on a domain name.
121
144
 
122
145
  ```ruby
@@ -124,6 +147,7 @@ result = email_hunter.people('stripe.com')
124
147
  ```
125
148
 
126
149
  #### Response Fields:
150
+
127
151
  ```ruby
128
152
  result.fetch(:employees)
129
153
  result.fetch(:position)
@@ -131,7 +155,177 @@ result.fetch(:email)
131
155
  result.fetch(:meta)
132
156
  ```
133
157
 
134
- ### 7. Account Information API
158
+ ### 7. Discover API (New in v2.0.0)
159
+
160
+ Search for companies using natural language queries.
161
+
162
+ ```ruby
163
+ result = email_hunter.discover('US-based Software companies', limit: 10)
164
+ ```
165
+
166
+ #### Response Fields:
167
+
168
+ ```ruby
169
+ result.data # Array of companies
170
+ result.meta.fetch(:results)
171
+ result.meta.fetch(:limit)
172
+ result.meta.fetch(:offset)
173
+ ```
174
+
175
+ ### 8. Leads Management API (New in v2.0.0)
176
+
177
+ Manage your leads stored in Hunter.
178
+
179
+ #### List Leads:
180
+
181
+ ```ruby
182
+ result = email_hunter.leads(limit: 20, offset: 0)
183
+ ```
184
+
185
+ #### Create Lead:
186
+
187
+ ```ruby
188
+ lead_data = {
189
+ email: 'john@example.com',
190
+ first_name: 'John',
191
+ last_name: 'Doe',
192
+ company: 'Example Inc',
193
+ position: 'CEO'
194
+ }
195
+ result = email_hunter.lead_create(lead_data)
196
+ ```
197
+
198
+ #### Update Lead:
199
+
200
+ ```ruby
201
+ update_data = { position: 'CTO' }
202
+ result = email_hunter.lead_update(lead_id, update_data)
203
+ ```
204
+
205
+ #### Delete Lead:
206
+
207
+ ```ruby
208
+ success = email_hunter.lead_delete(lead_id)
209
+ ```
210
+
211
+ #### Response Fields:
212
+
213
+ ```ruby
214
+ result.data.leads # Array of lead objects
215
+ result.data.leads.first.email
216
+ result.data.leads.first.company
217
+ result.meta.fetch(:count)
218
+ ```
219
+
220
+ ### 9. Campaigns Management API (New in v2.0.0)
221
+
222
+ Manage your email campaigns and recipients.
223
+
224
+ #### List Campaigns:
225
+
226
+ ```ruby
227
+ result = email_hunter.campaigns(limit: 20)
228
+ ```
229
+
230
+ #### Get Campaign Recipients:
231
+
232
+ ```ruby
233
+ result = email_hunter.campaign_recipients(campaign_id, limit: 20)
234
+ ```
235
+
236
+ #### Add Recipient to Campaign:
237
+
238
+ ```ruby
239
+ recipient_data = {
240
+ email: 'john@example.com',
241
+ first_name: 'John',
242
+ last_name: 'Doe'
243
+ }
244
+ result = email_hunter.campaign_add_recipient(campaign_id, recipient_data)
245
+ ```
246
+
247
+ #### Remove Recipient from Campaign:
248
+
249
+ ```ruby
250
+ success = email_hunter.campaign_delete_recipient(campaign_id, 'john@example.com')
251
+ ```
252
+
253
+ #### Response Fields:
254
+
255
+ ```ruby
256
+ result.data.campaigns # Array of campaign objects
257
+ result.data.recipients # Array of recipient objects
258
+ result.meta.fetch(:limit)
259
+ ```
260
+
261
+ ### 10. Lead Enrichment API (New in v2.0.0)
262
+
263
+ Enrich person data with 100+ attributes using email or LinkedIn.
264
+
265
+ ```ruby
266
+ # Using email
267
+ result = email_hunter.lead_enrichment(email: 'matt@hunter.io')
268
+
269
+ # Using LinkedIn
270
+ result = email_hunter.lead_enrichment(linkedin: 'matttharp')
271
+ ```
272
+
273
+ #### Response Fields:
274
+
275
+ ```ruby
276
+ result.data.name.fullName
277
+ result.data.email
278
+ result.data.location
279
+ result.data.timeZone
280
+ result.data.employment.domain
281
+ result.data.employment.title
282
+ result.data.employment.name
283
+ result.data.twitter.handle
284
+ result.data.linkedin.handle
285
+ result.meta.fetch(:email)
286
+ ```
287
+
288
+ ### 11. Company Enrichment API (New in v2.0.0)
289
+
290
+ Enrich company data with detailed firmographic information.
291
+
292
+ ```ruby
293
+ result = email_hunter.company_enrichment('stripe.com')
294
+ ```
295
+
296
+ #### Response Fields:
297
+
298
+ ```ruby
299
+ result.data.name
300
+ result.data.description
301
+ result.data.foundedYear
302
+ result.data.location
303
+ result.data.category.industry
304
+ result.data.metrics.employees
305
+ result.data.tech # Array of technologies used
306
+ result.data.site.emailAddresses
307
+ result.meta.fetch(:domain)
308
+ ```
309
+
310
+ ### 12. Combined Enrichment API (New in v2.0.0)
311
+
312
+ Get both lead and company data in a single call.
313
+
314
+ ```ruby
315
+ result = email_hunter.combined_enrichment(email: 'patrick@stripe.com')
316
+ ```
317
+
318
+ #### Response Fields:
319
+
320
+ ```ruby
321
+ result.lead.data # Lead enrichment data
322
+ result.company.data # Company enrichment data
323
+ result.meta.email
324
+ result.meta.domain
325
+ ```
326
+
327
+ ### 13. Account Information API
328
+
135
329
  Retrieve details about your Hunter account.
136
330
 
137
331
  ```ruby
@@ -139,21 +333,22 @@ result = email_hunter.account
139
333
  ```
140
334
 
141
335
  #### Response Example:
336
+
142
337
  ```json
143
338
  {
144
- "data": {
145
- "first_name": "Davide",
146
- "last_name": "Santangelo",
147
- "email": "davide.santangelo@gmail.com",
148
- "plan_name": "Free",
149
- "plan_level": 0,
150
- "reset_date": "2025-06-29",
151
- "team_id": 349,
152
- "calls": {
153
- "used": 4,
154
- "available": 50
155
- }
156
- }
339
+ "data": {
340
+ "first_name": "Davide",
341
+ "last_name": "Santangelo",
342
+ "email": "davide.santangelo@gmail.com",
343
+ "plan_name": "Free",
344
+ "plan_level": 0,
345
+ "reset_date": "2025-06-29",
346
+ "team_id": 349,
347
+ "calls": {
348
+ "used": 4,
349
+ "available": 50
350
+ }
351
+ }
157
352
  }
158
353
  ```
159
354
 
@@ -168,4 +363,3 @@ The EmailHunter gem is released under the [MIT License](https://opensource.org/l
168
363
  3. Commit your changes (`git commit -am 'Add new feature'`)
169
364
  4. Push to the branch (`git push origin my-new-feature`)
170
365
  5. Create a new Pull Request
171
-
data/Rakefile CHANGED
@@ -1 +1,3 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'bundler/gem_tasks'
data/bin/console CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
4
  require 'bundler/setup'
4
5
  require 'emailhunter'
data/emailhunter.gemspec CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Use require_relative to load the version file
2
4
  require_relative 'lib/email_hunter/version'
3
5
 
@@ -14,11 +16,11 @@ Gem::Specification.new do |spec|
14
16
  email discovery capabilities into your sales and marketing workflows.
15
17
  DESC
16
18
  spec.license = 'MIT'
17
-
19
+
18
20
  # Adding additional metadata improves discoverability
19
21
  spec.homepage = 'https://github.com/davidesantangelo/emailhunter'
20
22
  spec.metadata = {
21
- "source_code_uri" => "https://github.com/davidesantangelo/emailhunter"
23
+ 'source_code_uri' => 'https://github.com/davidesantangelo/emailhunter'
22
24
  }
23
25
 
24
26
  # Automatically include all version-controlled files except tests and specs
@@ -6,7 +6,7 @@ require 'json'
6
6
  module EmailHunter
7
7
  class Account
8
8
  API_URL = 'https://api.hunter.io/v2/account'
9
-
9
+
10
10
  attr_reader :result, :first_name, :last_name, :email, :plan_name, :plan_level, :reset_date, :team_id, :calls,
11
11
  :requests
12
12
 
@@ -28,10 +28,10 @@ module EmailHunter
28
28
 
29
29
  def parse_response(response)
30
30
  return nil unless response.success?
31
-
31
+
32
32
  data = JSON.parse(response.body, symbolize_names: true)
33
33
  return nil if data.empty?
34
-
34
+
35
35
  Struct.new(*data.keys).new(*data.values)
36
36
  end
37
37
  end
@@ -2,7 +2,8 @@
2
2
 
3
3
  require 'uri'
4
4
 
5
- %w[exist search finder verify count account company people].each do |file|
5
+ %w[exist search finder verify count account company people discover leads campaigns lead_enrichment company_enrichment
6
+ combined_enrichment].each do |file|
6
7
  require_relative file
7
8
  end
8
9
 
@@ -44,7 +45,7 @@ module EmailHunter
44
45
  def account
45
46
  execute_hunt(Account)
46
47
  end
47
-
48
+
48
49
  # Company Information API
49
50
  def company(domain)
50
51
  execute_hunt(Company, domain)
@@ -55,15 +56,75 @@ module EmailHunter
55
56
  execute_hunt(People, domain)
56
57
  end
57
58
 
59
+ # Discover API - Search companies using natural language
60
+ def discover(query, params = {})
61
+ execute_hunt(Discover, query, params)
62
+ end
63
+
64
+ # Leads API - List leads
65
+ def leads(params = {})
66
+ Leads.new(key, action: :list, params: params).hunt
67
+ end
68
+
69
+ # Leads API - Create lead
70
+ def lead_create(data)
71
+ Leads.new(key, action: :create, data: data).hunt
72
+ end
73
+
74
+ # Leads API - Update lead
75
+ def lead_update(id, data)
76
+ Leads.new(key, action: :update, lead_id: id, data: data).hunt
77
+ end
78
+
79
+ # Leads API - Delete lead
80
+ def lead_delete(id)
81
+ Leads.new(key, action: :delete, lead_id: id).hunt
82
+ end
83
+
84
+ # Campaigns API - List campaigns
85
+ def campaigns(params = {})
86
+ Campaigns.new(key, action: :list, params: params).hunt
87
+ end
88
+
89
+ # Campaigns API - Get campaign recipients
90
+ def campaign_recipients(campaign_id, params = {})
91
+ Campaigns.new(key, action: :recipients, campaign_id: campaign_id, params: params).hunt
92
+ end
93
+
94
+ # Campaigns API - Add recipient to campaign
95
+ def campaign_add_recipient(campaign_id, data)
96
+ Campaigns.new(key, action: :add_recipient, campaign_id: campaign_id, data: data).hunt
97
+ end
98
+
99
+ # Campaigns API - Delete recipient from campaign
100
+ def campaign_delete_recipient(campaign_id, email)
101
+ Campaigns.new(key, action: :delete_recipient, campaign_id: campaign_id, data: { email: email }).hunt
102
+ end
103
+
104
+ # Lead Enrichment API - Enrich person data
105
+ def lead_enrichment(email: nil, linkedin: nil)
106
+ LeadEnrichment.new(key, email: email, linkedin: linkedin).hunt
107
+ end
108
+
109
+ # Company Enrichment API - Enrich company data
110
+ def company_enrichment(domain)
111
+ CompanyEnrichment.new(domain, key).hunt
112
+ end
113
+
114
+ # Combined Enrichment API - Enrich both lead and company data
115
+ def combined_enrichment(email: nil, linkedin: nil)
116
+ CombinedEnrichment.new(key, email: email, linkedin: linkedin).hunt
117
+ end
118
+
58
119
  private
59
120
 
60
121
  # Helper method to reduce repetition
61
122
  def execute_hunt(klass, *args)
62
123
  case klass.name.split('::').last
63
- when 'Search'
64
- # Search expects (domain, key, params)
65
- domain, params = args
66
- klass.new(domain, key, params || {}).hunt
124
+ when 'Search', 'Discover'
125
+ # Search and Discover expect (value, key, params)
126
+ value, params = args
127
+ klass.new(value, key, params || {}).hunt
67
128
  when 'Count'
68
129
  # Count doesn't need key
69
130
  domain = args.first
@@ -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
@@ -7,7 +7,7 @@ require 'ostruct'
7
7
  module EmailHunter
8
8
  class Company
9
9
  API_URL = 'https://api.hunter.io/v2/companies/find'
10
-
10
+
11
11
  attr_reader :domain, :key
12
12
 
13
13
  def initialize(domain, key)
@@ -18,19 +18,19 @@ module EmailHunter
18
18
  def hunt
19
19
  response_data = fetch_company_data
20
20
  return nil if response_data.empty?
21
-
21
+
22
22
  # Convert nested data structure to a Struct
23
23
  data = response_data[:data]
24
24
  meta = response_data[:meta]
25
-
25
+
26
26
  result = OpenStruct.new(
27
27
  data: OpenStruct.new(data),
28
28
  meta: OpenStruct.new(meta)
29
29
  )
30
-
30
+
31
31
  # Recursively convert nested hashes to OpenStructs for deeper access
32
32
  convert_hash_to_struct(result.data, data)
33
-
33
+
34
34
  result
35
35
  end
36
36
 
@@ -40,7 +40,7 @@ module EmailHunter
40
40
  @fetch_company_data ||= begin
41
41
  connection = Faraday.new
42
42
  response = connection.get(API_URL, domain: domain, api_key: key)
43
-
43
+
44
44
  return {} unless response.success?
45
45
 
46
46
  JSON.parse(response.body, symbolize_names: true)
@@ -58,4 +58,4 @@ module EmailHunter
58
58
  end
59
59
  end
60
60
  end
61
- 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
@@ -6,7 +6,7 @@ require 'json'
6
6
  module EmailHunter
7
7
  class Count
8
8
  API_URL = 'https://api.hunter.io/v2/email-count'
9
-
9
+
10
10
  attr_reader :domain
11
11
 
12
12
  def initialize(domain)
@@ -16,7 +16,7 @@ module EmailHunter
16
16
  def hunt
17
17
  response_data = fetch_count_data
18
18
  return nil if response_data.empty?
19
-
19
+
20
20
  Struct.new(*response_data.keys).new(*response_data.values)
21
21
  end
22
22
 
@@ -26,7 +26,7 @@ module EmailHunter
26
26
  @fetch_count_data ||= begin
27
27
  connection = Faraday.new
28
28
  response = connection.get(API_URL, domain: domain)
29
-
29
+
30
30
  return {} unless response.success?
31
31
 
32
32
  JSON.parse(response.body, symbolize_names: true)
@@ -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
@@ -6,7 +6,7 @@ require 'json'
6
6
  module EmailHunter
7
7
  class Exist
8
8
  API_URL = 'https://api.emailhunter.co/v1/exist'
9
-
9
+
10
10
  attr_reader :email, :key
11
11
 
12
12
  def initialize(email, key)
@@ -17,7 +17,7 @@ module EmailHunter
17
17
  def hunt
18
18
  response_data = fetch_exist_data
19
19
  return nil if response_data.empty?
20
-
20
+
21
21
  Struct.new(*response_data.keys).new(*response_data.values)
22
22
  end
23
23
 
@@ -27,7 +27,7 @@ module EmailHunter
27
27
  @fetch_exist_data ||= begin
28
28
  connection = Faraday.new
29
29
  response = connection.get(API_URL, email: email, api_key: key)
30
-
30
+
31
31
  return {} unless response.success?
32
32
 
33
33
  JSON.parse(response.body, symbolize_names: true)
@@ -6,7 +6,7 @@ require 'json'
6
6
  module EmailHunter
7
7
  class Finder
8
8
  API_FINDER_URL = 'https://api.hunter.io/v2/email-finder'
9
-
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)
@@ -23,7 +23,7 @@ module EmailHunter
23
23
  def data
24
24
  @data ||= begin
25
25
  response = fetch_finder_data
26
-
26
+
27
27
  return {} unless response.success?
28
28
 
29
29
  JSON.parse(response.body, symbolize_names: true)
@@ -40,7 +40,7 @@ module EmailHunter
40
40
  last_name: last_name,
41
41
  api_key: key
42
42
  }
43
-
43
+
44
44
  connection.get(API_FINDER_URL, params)
45
45
  end
46
46
  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
@@ -0,0 +1,129 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'faraday'
4
+ require 'json'
5
+ require 'ostruct'
6
+
7
+ module EmailHunter
8
+ class Leads
9
+ API_URL = 'https://api.hunter.io/v2/leads'
10
+
11
+ attr_reader :key, :params, :action, :lead_id, :data
12
+
13
+ def initialize(key, action: :list, lead_id: nil, data: nil, params: {})
14
+ @key = key
15
+ @action = action
16
+ @lead_id = lead_id
17
+ @data = data
18
+ @params = params
19
+ end
20
+
21
+ def hunt
22
+ case action
23
+ when :list
24
+ list_leads
25
+ when :create
26
+ create_lead
27
+ when :update
28
+ update_lead
29
+ when :delete
30
+ delete_lead
31
+ else
32
+ raise ArgumentError, "Unknown action: #{action}"
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ def list_leads
39
+ response_data = fetch_leads_data
40
+ return nil if response_data.empty?
41
+
42
+ # Convert nested data structure to OpenStruct
43
+ result = OpenStruct.new(
44
+ data: OpenStruct.new(response_data[:data] || {}),
45
+ meta: OpenStruct.new(response_data[:meta] || {})
46
+ )
47
+
48
+ # Convert leads array to OpenStruct objects
49
+ result.data.leads = result.data.leads.map { |lead| OpenStruct.new(lead) } if result.data.leads.is_a?(Array)
50
+
51
+ result
52
+ end
53
+
54
+ def create_lead
55
+ response_data = post_lead_data
56
+ return nil if response_data.empty?
57
+
58
+ OpenStruct.new(response_data)
59
+ end
60
+
61
+ def update_lead
62
+ response_data = put_lead_data
63
+ return nil if response_data.empty?
64
+
65
+ OpenStruct.new(response_data)
66
+ end
67
+
68
+ def delete_lead
69
+ response = delete_lead_data
70
+ response.success?
71
+ end
72
+
73
+ def fetch_leads_data
74
+ @fetch_leads_data ||= begin
75
+ connection = Faraday.new
76
+ request_params = {
77
+ api_key: key,
78
+ limit: params.fetch(:limit, 20),
79
+ offset: params.fetch(:offset, 0)
80
+ }
81
+
82
+ # Add optional filters
83
+ request_params[:leads_list_id] = params[:leads_list_id] if params[:leads_list_id]
84
+
85
+ response = connection.get(API_URL, request_params)
86
+
87
+ return {} unless response.success?
88
+
89
+ JSON.parse(response.body, symbolize_names: true)
90
+ end
91
+ end
92
+
93
+ def post_lead_data
94
+ connection = Faraday.new
95
+ request_data = data.merge(api_key: key)
96
+
97
+ response = connection.post(API_URL) do |req|
98
+ req.headers['Content-Type'] = 'application/json'
99
+ req.body = request_data.to_json
100
+ end
101
+
102
+ return {} unless response.success?
103
+
104
+ JSON.parse(response.body, symbolize_names: true)
105
+ end
106
+
107
+ def put_lead_data
108
+ connection = Faraday.new
109
+ url = "#{API_URL}/#{lead_id}"
110
+ request_data = data.merge(api_key: key)
111
+
112
+ response = connection.put(url) do |req|
113
+ req.headers['Content-Type'] = 'application/json'
114
+ req.body = request_data.to_json
115
+ end
116
+
117
+ return {} unless response.success?
118
+
119
+ JSON.parse(response.body, symbolize_names: true)
120
+ end
121
+
122
+ def delete_lead_data
123
+ connection = Faraday.new
124
+ url = "#{API_URL}/#{lead_id}"
125
+
126
+ connection.delete(url, api_key: key)
127
+ end
128
+ end
129
+ end
@@ -7,7 +7,7 @@ require 'ostruct'
7
7
  module EmailHunter
8
8
  class People
9
9
  API_URL = 'https://api.hunter.io/v2/people/find'
10
-
10
+
11
11
  attr_reader :email, :key
12
12
 
13
13
  def initialize(email, key)
@@ -18,19 +18,19 @@ module EmailHunter
18
18
  def hunt
19
19
  response_data = fetch_people_data
20
20
  return nil if response_data.empty?
21
-
21
+
22
22
  # Convert nested data structure to a Struct
23
23
  data = response_data[:data]
24
24
  meta = response_data[:meta]
25
-
25
+
26
26
  result = OpenStruct.new(
27
27
  data: OpenStruct.new(data),
28
28
  meta: OpenStruct.new(meta)
29
29
  )
30
-
30
+
31
31
  # Recursively convert nested hashes to OpenStructs for deeper access
32
32
  convert_hash_to_struct(result.data, data)
33
-
33
+
34
34
  result
35
35
  end
36
36
 
@@ -40,7 +40,7 @@ module EmailHunter
40
40
  @fetch_people_data ||= begin
41
41
  connection = Faraday.new
42
42
  response = connection.get(API_URL, email: email, api_key: key)
43
-
43
+
44
44
  return {} unless response.success?
45
45
 
46
46
  JSON.parse(response.body, symbolize_names: true)
@@ -58,4 +58,4 @@ module EmailHunter
58
58
  end
59
59
  end
60
60
  end
61
- end
61
+ end
@@ -6,7 +6,7 @@ require 'json'
6
6
  module EmailHunter
7
7
  class Search
8
8
  API_URL = 'https://api.hunter.io/v2/domain-search'
9
-
9
+
10
10
  attr_reader :domain, :key, :params
11
11
 
12
12
  def initialize(domain, key, params = {})
@@ -18,7 +18,7 @@ module EmailHunter
18
18
  def hunt
19
19
  response_data = fetch_search_data
20
20
  return nil if response_data.empty?
21
-
21
+
22
22
  Struct.new(*response_data.keys).new(*response_data.values)
23
23
  end
24
24
 
@@ -41,10 +41,10 @@ module EmailHunter
41
41
  offset: offset,
42
42
  limit: limit
43
43
  }
44
-
44
+
45
45
  request_params[:type] = params[:type] if params[:type]
46
46
  response = connection.get(API_URL, request_params)
47
-
47
+
48
48
  return {} unless response.success?
49
49
 
50
50
  JSON.parse(response.body, symbolize_names: true)
@@ -6,7 +6,7 @@ require 'json'
6
6
  module EmailHunter
7
7
  class Verify
8
8
  API_URL = 'https://api.hunter.io/v2/email-verifier'
9
-
9
+
10
10
  attr_reader :email, :key
11
11
 
12
12
  def initialize(email, key)
@@ -17,7 +17,7 @@ module EmailHunter
17
17
  def hunt
18
18
  response_data = fetch_verify_data
19
19
  return nil if response_data.empty?
20
-
20
+
21
21
  Struct.new(*response_data.keys).new(*response_data.values)
22
22
  end
23
23
 
@@ -27,7 +27,7 @@ module EmailHunter
27
27
  @fetch_verify_data ||= begin
28
28
  connection = Faraday.new
29
29
  response = connection.get(API_URL, email: email, api_key: key)
30
-
30
+
31
31
  return {} unless response.success?
32
32
 
33
33
  JSON.parse(response.body, symbolize_names: true)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module EmailHunter
4
- VERSION = '1.1.0'
4
+ VERSION = '2.0.0'
5
5
  end
data/lib/emailhunter.rb CHANGED
@@ -1 +1,3 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require File.expand_path(File.join(File.dirname(__FILE__), './email_hunter'))
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: emailhunter
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Davide Santangelo
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-02-28 00:00:00.000000000 Z
11
+ date: 2025-11-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -132,10 +132,16 @@ files:
132
132
  - lib/email_hunter.rb
133
133
  - lib/email_hunter/account.rb
134
134
  - lib/email_hunter/api.rb
135
+ - lib/email_hunter/campaigns.rb
136
+ - lib/email_hunter/combined_enrichment.rb
135
137
  - lib/email_hunter/company.rb
138
+ - lib/email_hunter/company_enrichment.rb
136
139
  - lib/email_hunter/count.rb
140
+ - lib/email_hunter/discover.rb
137
141
  - lib/email_hunter/exist.rb
138
142
  - lib/email_hunter/finder.rb
143
+ - lib/email_hunter/lead_enrichment.rb
144
+ - lib/email_hunter/leads.rb
139
145
  - lib/email_hunter/people.rb
140
146
  - lib/email_hunter/search.rb
141
147
  - lib/email_hunter/verify.rb