piplapis-ruby 4.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.
@@ -0,0 +1,55 @@
1
+ require_relative 'version'
2
+
3
+ module Pipl
4
+
5
+ module Default
6
+
7
+ API_ENDPOINT = 'https://api.pipl.com/search/v4/'.freeze
8
+ API_KEY = 'sample_key'.freeze
9
+ USER_AGENT = "piplapis/ruby/#{Pipl::VERSION}".freeze
10
+
11
+ class << self
12
+
13
+ def options
14
+ Hash[Pipl::Configurable.keys.map{|key| [key, send(key)]}]
15
+ end
16
+
17
+ def api_key
18
+ ENV.fetch 'PIPL_API_KEY', API_KEY
19
+ end
20
+
21
+ def minimum_probability
22
+ ENV['PIPL_MINIMUM_PROBABILITY']
23
+ end
24
+
25
+ def minimum_match
26
+ ENV['PIPL_MINIMUM_MATCH']
27
+ end
28
+
29
+ def hide_sponsored
30
+ ENV['PIPL_HIDE_SPONSORED']
31
+ end
32
+
33
+ def live_feeds
34
+ ENV['PIPL_LIVE_FEEDS']
35
+ end
36
+
37
+ def show_sources
38
+ ENV['PIPL_SHOW_SOURCES']
39
+ end
40
+
41
+ def strict_validation
42
+ ENV['PIPL_USER_STRICT_VALIDATION']
43
+ end
44
+
45
+ def api_endpoint
46
+ ENV.fetch 'PIPL_API_ENDPOINT', API_ENDPOINT
47
+ end
48
+
49
+ def user_agent
50
+ ENV.fetch 'PIPL_USER_AGENT', USER_AGENT
51
+ end
52
+
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,33 @@
1
+ module Pipl
2
+
3
+ class AbstractMethodInvoked < StandardError;
4
+ end
5
+
6
+ class Client
7
+
8
+ class APIError < Exception
9
+ attr_reader :status_code
10
+
11
+ def initialize(message, status_code)
12
+ super message
13
+ @status_code = status_code
14
+ end
15
+
16
+ def is_user_error?
17
+ (400..499).member?(@status_code)
18
+ end
19
+
20
+ def is_pipl_error?
21
+ not is_user_error?
22
+ end
23
+
24
+ def self.from_json(json_str)
25
+ h = JSON.parse(json_str, symbolize_names: true)
26
+ self.new(h[:error], h[:@http_status_code])
27
+ end
28
+
29
+ end
30
+
31
+ end
32
+
33
+ end
@@ -0,0 +1,714 @@
1
+ require 'date'
2
+ require 'set'
3
+ require_relative 'consts'
4
+ require_relative 'utils'
5
+
6
+ module Pipl
7
+
8
+ class Field
9
+
10
+ include Pipl::Utils
11
+
12
+ attr_accessor :valid_since, :inferred
13
+
14
+ def initialize(params={})
15
+ @valid_since = params[:valid_since]
16
+ @inferred = params[:inferred]
17
+ end
18
+
19
+ def self.from_hash(h)
20
+ params = base_params_from_hash h
21
+ extra_metadata.each { |p| params[p] = h["@#{p}".to_sym] }
22
+ params = h.merge params
23
+ self.new(params)
24
+ end
25
+
26
+ def self.base_params_from_hash(h)
27
+ params = {
28
+ inferred: h[:@inferred],
29
+ type: h[:@type],
30
+ display: h[:display]
31
+ }
32
+ params[:valid_since] = Date.strptime(h[:@valid_since], Pipl::DATE_FORMAT) if h.key? :@valid_since
33
+ params[:date_range] = Pipl::DateRange.from_hash(h[:date_range]) if h.key? :date_range
34
+ params
35
+ end
36
+
37
+ def self.extra_metadata
38
+ []
39
+ end
40
+
41
+ def to_hash
42
+
43
+ end
44
+
45
+ def is_searchable?
46
+ true
47
+ end
48
+
49
+ end
50
+
51
+
52
+ class Name < Field
53
+ # @!attribute first
54
+ # @return [String] First name
55
+ # @!attribute middle
56
+ # @return [String] Middle name or initial
57
+ # @!attribute last
58
+ # @return [String] Last name
59
+ # @!attribute prefix
60
+ # @return [String] Name prefix
61
+ # @!attribute suffix
62
+ # @return [String] Name suffix
63
+ # @!attribute type
64
+ # @return [String] Type of association of this name to a person. One of `present`, `maiden`, `former` or `alias`.
65
+
66
+ attr_accessor :first, :middle, :last, :prefix, :suffix, :type, :raw, :display
67
+
68
+ def initialize(params={})
69
+ super params
70
+ @first = params[:first]
71
+ @middle = params[:middle]
72
+ @last = params[:last]
73
+ @prefix = params[:prefix]
74
+ @suffix = params[:suffix]
75
+ @type = params[:type]
76
+ @raw = params[:raw]
77
+ @display = params[:display]
78
+ end
79
+
80
+ def to_hash
81
+ {first: @first, middle: @middle, last: @last, prefix: @prefix, suffix: @suffix, raw: @raw}
82
+ .reject { |_, value| value.nil? }
83
+ end
84
+
85
+ def is_searchable?
86
+ first = Pipl::Utils.alpha_chars(@first || '')
87
+ last = Pipl::Utils.alpha_chars(@last || '')
88
+ raw = Pipl::Utils.alpha_chars(@raw || '')
89
+ (first.length > 1 and last.length > 1) or raw.length > 3
90
+ end
91
+
92
+ def to_s
93
+ return @display if @display
94
+
95
+ vals = [@prefix, @first, @middle, @last, @suffix]
96
+ s = vals.any? ? vals.select { |v| v }.map { |v| v.capitalize }.join(' ') : nil
97
+ s ? Pipl::Utils.to_utf8(s) : ''
98
+ end
99
+
100
+ end
101
+
102
+
103
+ class Address < Field
104
+ # @!attribute country
105
+ # @return [String] 2 letters country code
106
+ # @!attribute state
107
+ # @return [String] 2 letters state code
108
+ # @!attribute city
109
+ # @return [String] City
110
+ # @!attribute street
111
+ # @return [String] Street
112
+ # @!attribute house
113
+ # @return [String] House
114
+ # @!attribute apartment
115
+ # @return [String] Apartment
116
+ # @!attribute zip_code
117
+ # @return [String] Zip Code
118
+ # @!attribute po_box
119
+ # @return [String] Post Office box number
120
+ # @!attribute type
121
+ # @return [String] Type of association of this address to a person. One of `home`, `work` or `old`.
122
+ # @!attribute raw
123
+ # @return [String] Unparsed address.
124
+ # @!attribute display
125
+ # @return [String] well formatted representation of this address for display purposes.
126
+
127
+ attr_accessor :country, :state, :city, :street, :house, :apartment, :zip_code, :po_box, :type, :raw, :display
128
+
129
+ def initialize(params={})
130
+ super params
131
+ @country = params[:country]
132
+ @state = params[:state]
133
+ @city = params[:city]
134
+ @street = params[:street]
135
+ @house = params[:house]
136
+ @apartment = params[:apartment]
137
+ @zip_code = params[:zip_code]
138
+ @po_box = params[:po_box]
139
+ @type = params[:type]
140
+ @raw = params[:raw]
141
+ @display = params[:display]
142
+ end
143
+
144
+ def is_valid_country?
145
+ @country and Pipl::COUNTRIES.key? @country.upcase.to_sym
146
+ end
147
+
148
+ def is_valid_state?
149
+ is_valid_country? and Pipl::STATES.key?(@country.upcase.to_sym) and
150
+ @state and Pipl::STATES[@country.upcase.to_sym].key?(@state.upcase.to_sym)
151
+ end
152
+
153
+ def to_hash
154
+ {country: @country, state: @state, city: @city, street: @street, house: @house, apartment: @apartment,
155
+ zip_code: @zip_code, po_box: @po_box, raw: @raw}
156
+ .reject { |_, value| value.nil? }
157
+ end
158
+
159
+ def is_searchable?
160
+ (@raw and not @raw.empty?) or
161
+ (is_valid_country? and (@state.nil? or @state.empty? or is_valid_state?))
162
+ end
163
+
164
+ def country_full
165
+ Pipl::COUNTRIES[@country.upcase.to_sym] if @country
166
+ end
167
+
168
+ def state_full
169
+ Pipl::STATES[@country.upcase.to_sym][@state.upcase.to_sym] if is_valid_state?
170
+ end
171
+
172
+ def to_s
173
+ return @display if @display
174
+
175
+ country = @state ? @country : country_full
176
+ state = @city ? @state : state_full
177
+ vals = [@street, @city, state, country]
178
+ s = vals.any? ? vals.select { |v| v }.join(', ') : ''
179
+
180
+ if @street and (@house or @apartment)
181
+ prefix = [@house, @apartment].select { |v| v and not v.empty? }.join('-')
182
+ s = prefix + ' ' + (s || '')
183
+ end
184
+
185
+ if @po_box and @street.nil?
186
+ s = "P.O. Box #{@po_box} " + (s || '')
187
+ end
188
+
189
+ s ? Pipl::Utils.to_utf8(s) : ''
190
+ end
191
+
192
+ end
193
+
194
+ class Phone < Field
195
+ # @!attribute country_code
196
+ # @return [Fixnum] International country calling code
197
+ # @!attribute number
198
+ # @return [Fixnum] Actual phone number
199
+ # @!attribute extension
200
+ # @return [String] Office extension
201
+ # @!attribute type
202
+ # @return [String] Type of association of this phone to a person.
203
+ # Possible values are:
204
+ # mobile
205
+ # home_phone
206
+ # home_fax
207
+ # work_phone
208
+ # work_fax
209
+ # pager
210
+ # @!attribute raw
211
+ # @return [String] Unparsed phone number
212
+ # @!attribute display
213
+ # @return [String] Well formatted representation of this phone number for display purposes.
214
+ # @!attribute display_international
215
+ # @return [String] Well formatted international representation of this phone number for display purposes.
216
+
217
+ attr_accessor :country_code, :number, :extension, :type, :raw, :display, :display_international
218
+
219
+ def initialize(params={})
220
+ super params
221
+ @country_code = params[:country_code]
222
+ @number = params[:number]
223
+ @extension = params[:extension]
224
+ @type = params[:type]
225
+ @raw = params[:raw]
226
+ @display = params[:display]
227
+ @display_international = params[:display_international]
228
+ end
229
+
230
+ def self.extra_metadata
231
+ [:display_international]
232
+ end
233
+
234
+ def to_hash
235
+ {country_code: @country_code, number: @number, extension: @extension, raw: @raw}
236
+ .reject { |_, value| value.nil? }
237
+ end
238
+
239
+ def is_searchable?
240
+ (@raw and not @raw.empty?) or not @number.nil?
241
+ end
242
+
243
+ end
244
+
245
+ class Email < Field
246
+
247
+ RE_EMAIL = Regexp.new('^[\w.%\-+]+@[\w.%\-]+\.[a-zA-Z]{2,6}$')
248
+
249
+ # @!attribute address
250
+ # @return [String] Plain email address
251
+ # @!attribute address_md5
252
+ # @return [String] MD5 hash of the email address
253
+ # @!attribute type
254
+ # @return [String] Type of email association to a person. One of `personal` or `work`.
255
+ # @!attribute disposable
256
+ # @return [Boolean] Indicating if this email comes from a disposable email provider.
257
+ # @!attribute email_provider
258
+ # @return [Boolean] Indicating if this email comes from a well known email provider like gmail or yahoo.
259
+
260
+ attr_accessor :address, :address_md5, :type, :disposable, :email_provider
261
+
262
+ def initialize(params={})
263
+ super params
264
+ @address = params[:address]
265
+ @address_md5 = params[:address_md5]
266
+ @type = params[:type]
267
+ @disposable = params[:disposable]
268
+ @email_provider = params[:email_provider]
269
+ end
270
+
271
+ def self.extra_metadata
272
+ [:disposable, :email_provider]
273
+ end
274
+
275
+ def is_valid_email?
276
+ not RE_EMAIL.match(@address).nil?
277
+ end
278
+
279
+ def is_searchable?
280
+ is_valid_email? or (not @address_md5.nil? and @address_md5.length == 32)
281
+ end
282
+
283
+ def to_hash
284
+ {address: @address, address_md5: @address_md5}.reject { |_, value| value.nil? }
285
+ end
286
+
287
+ def username
288
+ @address.split('@')[0] if is_valid_email?
289
+ end
290
+
291
+ def domain
292
+ @address.split('@')[1] if is_valid_email?
293
+ end
294
+
295
+ end
296
+
297
+ class Job < Field
298
+
299
+ attr_accessor :title, :organization, :industry, :date_range, :display
300
+
301
+ def initialize(params={})
302
+ super params
303
+ @title = params[:title]
304
+ @organization = params[:organization]
305
+ @industry = params[:industry]
306
+ @date_range = params[:date_range]
307
+ @display = params[:display]
308
+ end
309
+
310
+ def to_hash
311
+ {title: @title, organization: @organization, industry: @industry,
312
+ date_range: @date_range ? @date_range.to_hash : nil}
313
+ .reject { |_, value| value.nil? }
314
+ end
315
+
316
+ def to_s
317
+ return @display if @display
318
+
319
+ if @title and @organization
320
+ s = @title + ' at ' + @organization
321
+ else
322
+ s = @title || @organization
323
+ end
324
+
325
+ if s and @industry
326
+ if @date_range
327
+ range = @date_range.years_range
328
+ s += ' (%s, %d-%d)' % [@industry, range[0], range[1]]
329
+ else
330
+ s += ' (%s)' % [@industry]
331
+ end
332
+ else
333
+ s = ((s || '') + ' ' + (@industry || '')).strip
334
+ if s and @date_range
335
+ range = @date_range.years_range
336
+ s += ' (%d-%d)' % [range[0], range[1]]
337
+ end
338
+ end
339
+
340
+ s ? Pipl::Utils.to_utf8(s) : ''
341
+ end
342
+
343
+ end
344
+
345
+
346
+ class Education < Field
347
+
348
+ attr_accessor :degree, :school, :date_range, :display
349
+
350
+ def initialize(params={})
351
+ super params
352
+ @degree = params[:degree]
353
+ @school = params[:school]
354
+ @date_range = params[:date_range]
355
+ @display = params[:display]
356
+ end
357
+
358
+ def to_hash
359
+ {degree: @degree, school: @school, date_range: @date_range ? @date_range.to_hash : nil}
360
+ .reject { |_, value| value.nil? }
361
+ end
362
+
363
+ def to_s
364
+ return @display if @display
365
+
366
+ if @degree and @school
367
+ s = @degree + ' from ' + @school
368
+ else
369
+ s = @degree || @school
370
+ end
371
+
372
+ if s and @date_range
373
+ range = @date_range.years_range
374
+ s += ' (%d-%d)' % [range[0], range[1]]
375
+ end
376
+
377
+ s ? Pipl::Utils.to_utf8(s) : ''
378
+ end
379
+
380
+ end
381
+
382
+
383
+ class Image < Field
384
+
385
+ attr_accessor :url, :thumbnail_token
386
+
387
+ def initialize(params={})
388
+ super params
389
+ @url = params[:url]
390
+ @thumbnail_token = params[:thumbnail_token]
391
+ end
392
+
393
+ def thumbnail_url(params={})
394
+ return unless @thumbnail_token
395
+
396
+ opts = {width: 100, height: 100, favicon: true, zoom_face: true, use_https: false}.merge(params)
397
+ schema = opts.delete(:use_https) ? 'https': 'http'
398
+ query_params = ["token=#{@thumbnail_token}"] + opts.map { |k, v| "#{k}=#{v}" unless v.nil? }
399
+ "#{schema}://thumb.pipl.com/image?#{query_params.compact.join('&')}"
400
+ end
401
+
402
+ end
403
+
404
+
405
+ class Username < Field
406
+
407
+ attr_accessor :content
408
+
409
+ def initialize(params={})
410
+ super params
411
+ @content = params[:content]
412
+ end
413
+
414
+ def to_hash
415
+ {content: @content} if @content
416
+ end
417
+
418
+ def is_searchable?
419
+ @content and Pipl::Utils.alnum_chars(@content).length > 3
420
+ end
421
+
422
+ end
423
+
424
+
425
+ class UserID < Username
426
+
427
+ def is_searchable?
428
+ false
429
+ end
430
+
431
+ end
432
+
433
+
434
+ class DOB < Field
435
+
436
+ attr_accessor :date_range, :display
437
+
438
+ def initialize(params={})
439
+ super params
440
+ @date_range = params[:date_range]
441
+ @display = params[:display]
442
+ end
443
+
444
+ def self.from_birth_year(birth_year)
445
+ raise ArgumentError.new('birth_year must be positive') unless birth_year > 0
446
+ self.new({date_range: DateRange.from_years_range(birth_year, birth_year)})
447
+ end
448
+
449
+ def self.from_birth_date(birth_date)
450
+ raise ArgumentError.new('birth_date can\'t be in the future') unless birth_date <= Date.today
451
+ self.new({date_range: DateRange.new(birth_date, birth_date)})
452
+ end
453
+
454
+ def self.from_age(age)
455
+ self.from_age_range(age, age)
456
+ end
457
+
458
+ def self.from_age_range(start_age, end_age)
459
+ raise ArgumentError.new('start_age and end_age can\'t be negative') if start_age < 0 or end_age < 0
460
+
461
+ if start_age > end_age
462
+ start_age, end_age = end_age, start_age
463
+ end
464
+
465
+ today = Date.today
466
+ start_date = today << end_age * 12
467
+ start_date = start_date - 1
468
+ end_date = today << start_age * 12
469
+ self.new({date_range: Pipl::DateRange.new(start_date, end_date)})
470
+ end
471
+
472
+ def to_s
473
+ @display or Pipl::Utils.to_utf8(age.to_s)
474
+ end
475
+
476
+ def age
477
+ unless @date_range.nil?
478
+ dob = @date_range.middle
479
+ today = Date.today
480
+ diff = today.year - dob.year
481
+ diff = diff - 1 if dob.month > today.month or (dob.month >= today.month and dob.day > today.day)
482
+ diff
483
+ end
484
+ end
485
+
486
+ def age_range
487
+ if @date_range
488
+ start_age = DOB.new({date_range: Pipl::DateRange.new(@date_range.start, @date_range.start)}).age
489
+ end_age = DOB.new({date_range: Pipl::DateRange.new(@date_range.end, @date_range.end)}).age
490
+ return end_age, start_age
491
+ else
492
+ return nil, nil
493
+ end
494
+ end
495
+
496
+ def to_hash
497
+ {date_range: @date_range.to_hash} if @date_range
498
+ end
499
+
500
+ def is_searchable?
501
+ not @date_range.nil?
502
+ end
503
+
504
+ end
505
+
506
+
507
+ class Url < Field
508
+ # @!attribute url
509
+ # @return [String] Actual Url
510
+ # @!attribute category
511
+ # @return [String] Category of the domain
512
+ # Possible values are:
513
+ # background_reports
514
+ # contact_details
515
+ # email_address
516
+ # media
517
+ # personal_profiles
518
+ # professional_and_business
519
+ # public_records
520
+ # publications
521
+ # school_and_classmates
522
+ # web_pages
523
+ # @!attribute domain
524
+ # @return [String] Canonical domain of the url
525
+ # @!attribute name
526
+ # @return [String] Name of the website hosting the url
527
+ # @!attribute sponsored
528
+ # @return [Boolean] Indicate if this url comes from a sponsored data source
529
+ # @!attribute sponsored
530
+ # @return [String] Unique identifier of this url
531
+
532
+ attr_accessor :url, :category, :domain, :name, :sponsored, :source_id
533
+
534
+ def initialize(params={})
535
+ super params
536
+ @url = params[:url]
537
+ @category = params[:category]
538
+ @domain = params[:domain]
539
+ @name = params[:name]
540
+ @sponsored = params[:sponsored]
541
+ @source_id = params[:source_id]
542
+ end
543
+
544
+ def self.extra_metadata
545
+ [:category, :domain, :name, :sponsored, :source_id]
546
+ end
547
+
548
+ end
549
+
550
+
551
+ class Gender < Field
552
+
553
+ attr_accessor :content
554
+
555
+ def initialize(params={})
556
+ super params
557
+ raise ArgumentError.new("#{params[:content]} is not a valid gender") \
558
+ unless %w(male female).include? params[:content]
559
+ @content = params[:content]
560
+ end
561
+
562
+ def to_s
563
+ Pipl::Utils.titleize @content if @content
564
+ end
565
+
566
+ def to_hash
567
+ {content: @content} if @content
568
+ end
569
+
570
+ end
571
+
572
+
573
+ class Ethnicity < Field
574
+
575
+ # @!attribute content
576
+ # @return [String] Ethnicity name based on the U.S Census Bureau.
577
+ # Possible values are:
578
+ # white
579
+ # black
580
+ # american_indian
581
+ # alaska_native
582
+ # asian_indian
583
+ # chinese
584
+ # filipino
585
+ # other_asian
586
+ # japanese
587
+ # korean
588
+ # vietnamese
589
+ # native_hawaiian
590
+ # guamanian
591
+ # chamorro
592
+ # samoan
593
+ # other_pacific_islander
594
+ # other
595
+
596
+ attr_accessor :content
597
+
598
+ def initialize(params={})
599
+ super params
600
+ @content = params[:content]
601
+ end
602
+
603
+ def to_s
604
+ Pipl::Utils.titleize @content.gsub(/_/, ' ') if @content
605
+ end
606
+
607
+ end
608
+
609
+
610
+ class Language < Field
611
+
612
+ attr_accessor :language, :region, :display
613
+
614
+ def initialize(params={})
615
+ super params
616
+ @language = params[:language]
617
+ @region = params[:region]
618
+ @display = params[:display]
619
+ end
620
+
621
+ def to_s
622
+ return @display if @display
623
+ return "#{@language}_#{@region}" if @language and @region
624
+ return @language if @language and not @language.empty?
625
+ @region
626
+ end
627
+
628
+ end
629
+
630
+ class OriginCountry < Field
631
+
632
+ attr_accessor :country
633
+
634
+ def initialize(params={})
635
+ super params
636
+ @country = params[:country]
637
+ end
638
+
639
+ def to_s
640
+ Pipl::COUNTRIES[@country.upcase.to_sym] if @country
641
+ end
642
+
643
+ end
644
+
645
+
646
+ class Tag < Field
647
+
648
+ attr_accessor :content, :classification
649
+
650
+ def initialize(params={})
651
+ super params
652
+ @content = params[:content]
653
+ @classification = params[:classification]
654
+ end
655
+
656
+ def self.extra_metadata
657
+ [:classification]
658
+ end
659
+
660
+ def to_s
661
+ @content
662
+ end
663
+
664
+ end
665
+
666
+
667
+ class DateRange
668
+
669
+ attr_reader :start, :end
670
+
671
+ def initialize(start, end_)
672
+ raise ArgumentError.new('Start/End parameters missing') unless start and end_
673
+
674
+ @start = start
675
+ @end = end_
676
+ if @start > @end
677
+ @start, @end = @end, @start
678
+ end
679
+ end
680
+
681
+ def ==(other)
682
+ other.instance_of?(self.class) and inspect == other.inspect
683
+ end
684
+
685
+ alias_method :eql?, :==
686
+
687
+ def is_exact?
688
+ @start == @end
689
+ end
690
+
691
+ def middle
692
+ @start + ((@end - @start) / 2)
693
+ end
694
+
695
+ def years_range
696
+ return @start.year, @end.year
697
+ end
698
+
699
+ def self.from_years_range(start_year, end_year)
700
+ self.new(Date.new(start_year, 1, 1), Date.new(end_year, 12, 31))
701
+ end
702
+
703
+ def self.from_hash(h)
704
+ start_, end_ = h[:start], h[:end]
705
+ raise ArgumentError.new('DateRange must have both start and end') unless start_ and end_
706
+ self.new(Date.strptime(start_, Pipl::DATE_FORMAT), Date.strptime(end_, Pipl::DATE_FORMAT))
707
+ end
708
+
709
+ def to_hash
710
+ {start: @start.strftime(Pipl::DATE_FORMAT), end: @end.strftime(Pipl::DATE_FORMAT)}
711
+ end
712
+ end
713
+
714
+ end