kickboxer 0.0.1
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.
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +53 -0
- data/Rakefile +13 -0
- data/kickboxer.gemspec +21 -0
- data/lib/kickboxer.rb +5 -0
- data/lib/kickboxer/config.rb +19 -0
- data/lib/kickboxer/name.rb +118 -0
- data/lib/kickboxer/person.rb +67 -0
- data/lib/kickboxer/request.rb +44 -0
- data/lib/kickboxer/response.rb +49 -0
- data/lib/kickboxer/version.rb +3 -0
- data/spec/fixtures/name-deduce-email.json +14 -0
- data/spec/fixtures/name-normalize.json +26 -0
- data/spec/fixtures/name-parse.json +10 -0
- data/spec/fixtures/name-similarity.json +45 -0
- data/spec/fixtures/name-stats-ambiguous.json +64 -0
- data/spec/fixtures/name-stats-both.json +62 -0
- data/spec/fixtures/name-stats-family.json +12 -0
- data/spec/fixtures/name-stats-given.json +57 -0
- data/spec/fixtures/person-email.json +450 -0
- data/spec/fixtures/person-facebook.json +122 -0
- data/spec/fixtures/person-phone.json +410 -0
- data/spec/fixtures/person-twitter.json +231 -0
- data/spec/kickboxer/config_spec.rb +12 -0
- data/spec/kickboxer/name_spec.rb +141 -0
- data/spec/kickboxer/person_spec.rb +87 -0
- data/spec/kickboxer/request_spec.rb +24 -0
- data/spec/kickboxer/response_spec.rb +40 -0
- data/spec/spec_helper.rb +18 -0
- metadata +127 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Solomon White
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
# Kickboxer
|
2
|
+
|
3
|
+
Kickboxer is a wrapper around the FullContact API
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'kickboxer'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install kickboxer
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
The Full Contact API requires an API key. Register for a free key at
|
22
|
+
https://www.fullcontact.com/sign-up/. Once you receive your key, you can
|
23
|
+
configure Kickboxer to use it via:
|
24
|
+
|
25
|
+
Kickboxer.api_key = '01234decafbad'
|
26
|
+
|
27
|
+
|
28
|
+
The following API endpoints are implemented:
|
29
|
+
|
30
|
+
+ Person
|
31
|
+
+ lookup (by email, phone number, twitter username, or facebook username)
|
32
|
+
- User (TODO)
|
33
|
+
- Contact List (TODO)
|
34
|
+
- Contact (TODO)
|
35
|
+
- Snapshot (TODO)
|
36
|
+
- Subscription (TODO)
|
37
|
+
+ Name
|
38
|
+
+ normalize
|
39
|
+
+ deduce
|
40
|
+
+ similarity
|
41
|
+
+ stats
|
42
|
+
+ parser
|
43
|
+
- Icon (TODO)
|
44
|
+
- Provisioning (TODO)
|
45
|
+
- Batch Process (TODO)
|
46
|
+
|
47
|
+
## Contributing
|
48
|
+
|
49
|
+
1. Fork it
|
50
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
51
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
52
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
53
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
require 'rspec/core/rake_task'
|
3
|
+
|
4
|
+
desc "Run specs"
|
5
|
+
task :spec do
|
6
|
+
RSpec::Core::RakeTask.new(:spec) do |t|
|
7
|
+
t.rspec_opts = %w{--colour --format progress}
|
8
|
+
t.pattern = 'spec/**/*_spec.rb'
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
desc "Default: run specs."
|
13
|
+
task :default => :spec
|
data/kickboxer.gemspec
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/kickboxer/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Solomon White"]
|
6
|
+
gem.email = ["rubysolo@gmail.com"]
|
7
|
+
gem.description = %q{Kickboxer}
|
8
|
+
gem.summary = %q{Kickboxer is a Ruby library for accessing the FullContact API}
|
9
|
+
gem.homepage = "https://github.com/rubysolo/kickboxer"
|
10
|
+
|
11
|
+
gem.add_development_dependency 'pry'
|
12
|
+
gem.add_development_dependency 'rake'
|
13
|
+
gem.add_development_dependency 'rspec'
|
14
|
+
|
15
|
+
gem.files = `git ls-files`.split($\)
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
|
+
gem.name = "kickboxer"
|
19
|
+
gem.require_paths = ["lib"]
|
20
|
+
gem.version = Kickboxer::VERSION
|
21
|
+
end
|
data/lib/kickboxer.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
module Kickboxer
|
2
|
+
def self.api_key=(value)
|
3
|
+
@api_key = value
|
4
|
+
end
|
5
|
+
|
6
|
+
def self.api_key
|
7
|
+
@api_key
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.debug=(value)
|
11
|
+
@debug = value
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.debug
|
15
|
+
@debug
|
16
|
+
end
|
17
|
+
|
18
|
+
class NoApiKeyError < RuntimeError; end
|
19
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
require 'kickboxer/request'
|
2
|
+
require 'kickboxer/response'
|
3
|
+
|
4
|
+
module Kickboxer
|
5
|
+
class Name
|
6
|
+
# Public: The Name Normalization method takes quasi structured name data
|
7
|
+
# provided as a string and outputs the data in a structured manner. It also
|
8
|
+
# returns a likelihood based only on the order of the given name and family
|
9
|
+
# name as seen in the US population.
|
10
|
+
#
|
11
|
+
# full_name - a string containing the full name. Can include standard
|
12
|
+
# prefix, first name, nickname, middle name, last name, and
|
13
|
+
# suffix.
|
14
|
+
#
|
15
|
+
# Example
|
16
|
+
#
|
17
|
+
# response = Kickboxer::Name.normalize('Mr. John (Johnny) Michael Smith')
|
18
|
+
# response.nameDetails.givenName
|
19
|
+
# # => 'John'
|
20
|
+
# response.nameDetails.familyName
|
21
|
+
# # => 'Smith'
|
22
|
+
def self.normalize(full_name)
|
23
|
+
Request.run('name/normalizer', q: full_name)
|
24
|
+
end
|
25
|
+
|
26
|
+
# Public: The Name Deducer method takes a username or email address
|
27
|
+
# provided as a string and attempts to deduce a structured name. It also
|
28
|
+
# returns a likelihood based on US population data. This method is ideal
|
29
|
+
# for business email addresses due to the use of standard convention in
|
30
|
+
# corporate email address formats.
|
31
|
+
#
|
32
|
+
# options - a hash containing the email or username to be interpreted.
|
33
|
+
# :email - the email address
|
34
|
+
# :username - the username
|
35
|
+
# either :email or :username must be populated
|
36
|
+
#
|
37
|
+
# Example
|
38
|
+
#
|
39
|
+
# response = Kickboxer::Name.deduce(username: 'johndsmith79')
|
40
|
+
# response.nameDetails.givenName
|
41
|
+
# # => 'John'
|
42
|
+
# response.nameDetails.familyName
|
43
|
+
# # => 'Smith'
|
44
|
+
def self.deduce(options={})
|
45
|
+
require_any_key options, :email, :username
|
46
|
+
Request.run('name/deducer', options)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Public: The Name Similarity endpoint compares two names and returns a
|
50
|
+
# score indicating how similar they are. As the performance of different
|
51
|
+
# similarity algorithms can vary over different data sets, the endpoint
|
52
|
+
# provides 3 separate choices.
|
53
|
+
#
|
54
|
+
# first - a string containing the first name to compare
|
55
|
+
# second - a string containing the second name to compare
|
56
|
+
#
|
57
|
+
# Example
|
58
|
+
#
|
59
|
+
# response = Kickboxer::Name.similarity('john', 'johnathan')
|
60
|
+
# response.result.SimMetrics.jaroWinkler.similarity
|
61
|
+
# # => 0.8889
|
62
|
+
# response.result.SecondString.jaroWinkler.similarity
|
63
|
+
# # => 0.8889
|
64
|
+
# response.result.FullContact.BigramAnalysis.dice.similarity
|
65
|
+
# # => 0.5454545455
|
66
|
+
def self.similarity(first, second)
|
67
|
+
Request.run('name/similarity', q1: first, q2: second)
|
68
|
+
end
|
69
|
+
|
70
|
+
# Public: The Name Stats method has a variety of parameters that can be
|
71
|
+
# used to determine more about a name. See the Full Contact documentation
|
72
|
+
# page (http://www.fullcontact.com/docs/?category=name) for a list of
|
73
|
+
# available statistics.
|
74
|
+
#
|
75
|
+
# options - a hash containing the name or name parts to be examined.
|
76
|
+
# :givenName - contains a name believed to be a first name.
|
77
|
+
# :familyName - contains a name believed to be a last name.
|
78
|
+
# :name - contains a name of unknown type.
|
79
|
+
# At least one of the above must be provided. Both given and
|
80
|
+
# family name can be provided, if available.
|
81
|
+
#
|
82
|
+
# Example
|
83
|
+
#
|
84
|
+
# response = Kickboxer::Name.stats(name: 'john')
|
85
|
+
# response.name.given.likelihood # likelihood this is a given name
|
86
|
+
# # => 0.992
|
87
|
+
# response.name.family.likelihood # likelihood this is a family name
|
88
|
+
# # => 0.008
|
89
|
+
def self.stats(options={})
|
90
|
+
require_any_key options, :name, :givenName, :familyName
|
91
|
+
Request.run('name/stats', options)
|
92
|
+
end
|
93
|
+
|
94
|
+
# Public: The Name Parser method can be used when you have two names but
|
95
|
+
# don't know which one is the first name and which is the last name.
|
96
|
+
#
|
97
|
+
# full_name - a string containing the full name in unknown order
|
98
|
+
#
|
99
|
+
# Example
|
100
|
+
#
|
101
|
+
# response = Kickboxer::Name.parse('smith john')
|
102
|
+
# response.result.givenName
|
103
|
+
# # => 'John'
|
104
|
+
# response.result.familyName
|
105
|
+
# # => 'Smith'
|
106
|
+
# response.result.likelihood
|
107
|
+
# # => 1
|
108
|
+
def self.parse(full_name)
|
109
|
+
Request.run('name/parser', q: full_name)
|
110
|
+
end
|
111
|
+
|
112
|
+
private
|
113
|
+
|
114
|
+
def self.require_any_key(options, *keys)
|
115
|
+
raise "#{ keys.map{|k| ":#{ k }" }.join(' or ') } required" if (options.keys && keys).empty?
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module Kickboxer
|
2
|
+
class Person
|
3
|
+
# Public: look up a person by email address
|
4
|
+
#
|
5
|
+
# email_address - a string containing the email address
|
6
|
+
#
|
7
|
+
# Example
|
8
|
+
#
|
9
|
+
# response = Kickboxer::Person.find_by_email('bart@fullcontact.com')
|
10
|
+
# response.contactInfo.familyName
|
11
|
+
# # => 'Lorang'
|
12
|
+
# response.contactInfo.givenName
|
13
|
+
# # => 'Bart'
|
14
|
+
#
|
15
|
+
def self.find_by_email(email_address)
|
16
|
+
Request.run('person', email: email_address)
|
17
|
+
end
|
18
|
+
|
19
|
+
# Public: look up a person by phone number
|
20
|
+
#
|
21
|
+
# phone_number - a string containing the phone number
|
22
|
+
#
|
23
|
+
# Example
|
24
|
+
#
|
25
|
+
# response = Kickboxer::Person.find_by_phone_number('+13037170414')
|
26
|
+
# response.contactInfo.familyName
|
27
|
+
# # => 'Lorang'
|
28
|
+
# response.contactInfo.givenName
|
29
|
+
# # => 'Bart'
|
30
|
+
#
|
31
|
+
def self.find_by_phone_number(phone_number)
|
32
|
+
Request.run('person', phone: phone_number)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Public: look up a person by twitter username
|
36
|
+
#
|
37
|
+
# username - a string containing the twitter username
|
38
|
+
#
|
39
|
+
# Example
|
40
|
+
#
|
41
|
+
# response = Kickboxer::Person.find_by_twitter('lorangb')
|
42
|
+
# response.contactInfo.familyName
|
43
|
+
# # => 'Lorang'
|
44
|
+
# response.contactInfo.givenName
|
45
|
+
# # => 'Bart'
|
46
|
+
#
|
47
|
+
def self.find_by_twitter(username)
|
48
|
+
Request.run('person', twitter: username)
|
49
|
+
end
|
50
|
+
|
51
|
+
# Public: look up a person by facebook username
|
52
|
+
#
|
53
|
+
# username - a string containing the facebook username
|
54
|
+
#
|
55
|
+
# Example
|
56
|
+
#
|
57
|
+
# response = Kickboxer::Person.find_by_facebook('bart.lorang')
|
58
|
+
# response.contactInfo.familyName
|
59
|
+
# # => 'Lorang'
|
60
|
+
# response.contactInfo.givenName
|
61
|
+
# # => 'Bart'
|
62
|
+
#
|
63
|
+
def self.find_by_facebook(username)
|
64
|
+
Request.run('person', facebookUsername: username)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'kickboxer/config'
|
2
|
+
require 'kickboxer/response'
|
3
|
+
|
4
|
+
require 'open-uri'
|
5
|
+
require 'json'
|
6
|
+
|
7
|
+
module Kickboxer
|
8
|
+
class Request
|
9
|
+
BASE_URL = "https://api.fullcontact.com/v2"
|
10
|
+
|
11
|
+
def initialize(action)
|
12
|
+
@action = action
|
13
|
+
end
|
14
|
+
|
15
|
+
def build_url(options={})
|
16
|
+
options[:apiKey] = Kickboxer.api_key
|
17
|
+
raise NoApiKeyError unless options[:apiKey]
|
18
|
+
|
19
|
+
querystring = options.map{|k,v| "#{ k }=#{ URI.escape v }" } * '&'
|
20
|
+
"#{ BASE_URL }/#{ @action }.json?#{ querystring }"
|
21
|
+
end
|
22
|
+
|
23
|
+
def run(options={})
|
24
|
+
debug = options.delete(:debug) || Kickboxer.debug
|
25
|
+
|
26
|
+
response = open build_url(options)
|
27
|
+
body = response.read
|
28
|
+
|
29
|
+
if debug
|
30
|
+
filename = debug.is_a?(String) ? debug : @action.gsub('/', '-')
|
31
|
+
File.open("#{ filename }.json","w"){|out| out.puts body }
|
32
|
+
end
|
33
|
+
|
34
|
+
data = JSON.parse(body)
|
35
|
+
|
36
|
+
code, text = response.status
|
37
|
+
Response.new data.update(status_code: code.to_i, status_text: text)
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.run(action, options={})
|
41
|
+
new(action).run(options)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Kickboxer
|
2
|
+
class Response
|
3
|
+
def initialize(data={}, &block)
|
4
|
+
@data = normalize data
|
5
|
+
@data.merge!(Collector.new.instance_eval(&block).data) if block_given?
|
6
|
+
end
|
7
|
+
|
8
|
+
def respond_to?(method_id)
|
9
|
+
@data.has_key?(method_id.to_sym) || @data.has_key?(method_id.to_s) || super
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def normalize(hash)
|
15
|
+
hash.inject({}) do |h, (k,v)|
|
16
|
+
h.update(normalize_key(k) => normalize_value(v))
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def normalize_key(key)
|
21
|
+
key.to_sym
|
22
|
+
end
|
23
|
+
|
24
|
+
def normalize_value(value)
|
25
|
+
value.is_a?(Hash) ? Response.new(normalize(value)) : value
|
26
|
+
end
|
27
|
+
|
28
|
+
def method_missing(key, *args)
|
29
|
+
case
|
30
|
+
when @data.has_key?(key.to_sym)
|
31
|
+
@data[key.to_sym]
|
32
|
+
when @data.has_key?(key.to_s)
|
33
|
+
@data[key.to_s]
|
34
|
+
else
|
35
|
+
super
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class Collector
|
40
|
+
attr_reader :data
|
41
|
+
|
42
|
+
def method_missing(key, value)
|
43
|
+
@data ||= {}
|
44
|
+
@data[key] = value
|
45
|
+
self
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|