possible_email 0.0.2
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 +7 -0
- data/.coveralls.yml +1 -0
- data/.gitignore +17 -0
- data/.rspec +3 -0
- data/.travis.yml +6 -0
- data/Gemfile +2 -0
- data/Guardfile +11 -0
- data/LICENSE.txt +22 -0
- data/README.md +161 -0
- data/Rakefile +13 -0
- data/bin/possible_email +6 -0
- data/lib/possible_email.rb +56 -0
- data/lib/possible_email/cli.rb +33 -0
- data/lib/possible_email/error.rb +3 -0
- data/lib/possible_email/patterns.rb +38 -0
- data/lib/possible_email/permutator.rb +64 -0
- data/lib/possible_email/profile/image.rb +11 -0
- data/lib/possible_email/profile/membership.rb +12 -0
- data/lib/possible_email/profile/occupation.rb +10 -0
- data/lib/possible_email/profile/phone.rb +5 -0
- data/lib/possible_email/profile/profile.rb +58 -0
- data/lib/possible_email/rapportive_requester.rb +49 -0
- data/lib/possible_email/response.rb +26 -0
- data/lib/possible_email/response_getter.rb +37 -0
- data/lib/possible_email/version.rb +3 -0
- data/possible_email.gemspec +35 -0
- data/spec/PossibleEmail/possible_email/permutator_spec.rb +83 -0
- data/spec/PossibleEmail/possible_email/profile/image_spec.rb +20 -0
- data/spec/PossibleEmail/possible_email/profile/membership_spec.rb +26 -0
- data/spec/PossibleEmail/possible_email/profile/occupation_spec.rb +15 -0
- data/spec/PossibleEmail/possible_email/profile/phone_spec.rb +0 -0
- data/spec/PossibleEmail/possible_email/profile/profile_spec.rb +100 -0
- data/spec/PossibleEmail/possible_email/rapportive_requester_spec.rb +67 -0
- data/spec/PossibleEmail/possible_email/response_getter_spec.rb +50 -0
- data/spec/PossibleEmail/possible_email/response_spec.rb +22 -0
- data/spec/PossibleEmail/possible_email_spec.rb +92 -0
- data/spec/fixtures/rapportive_example_data.json +509 -0
- data/spec/spec_helper.rb +12 -0
- metadata +274 -0
@@ -0,0 +1,12 @@
|
|
1
|
+
module PossibleEmail
|
2
|
+
class Membership
|
3
|
+
attr_reader :profile_url, :profile_id, :username, :site_name
|
4
|
+
|
5
|
+
def initialize(data)
|
6
|
+
@profile_url = data['profile_url']
|
7
|
+
@profile_id = data['profile_id']
|
8
|
+
@username = data['username']
|
9
|
+
@site_name = data['site_name']
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'possible_email/profile/membership'
|
2
|
+
require 'possible_email/profile/occupation'
|
3
|
+
require 'possible_email/profile/image'
|
4
|
+
require 'possible_email/profile/phone'
|
5
|
+
|
6
|
+
module PossibleEmail
|
7
|
+
class NoDataError < ArgumentError; end
|
8
|
+
|
9
|
+
class Profile
|
10
|
+
attr_reader :data, :email, :name, :first_name, :last_name, :friendly_name,
|
11
|
+
:location, :headline, :success, :occupations, :memberships,
|
12
|
+
:images, :phones
|
13
|
+
|
14
|
+
def initialize(data)
|
15
|
+
@data = data
|
16
|
+
|
17
|
+
fail NoDataError, 'Data given was empty' if @data.empty?
|
18
|
+
|
19
|
+
retrieve_attribute
|
20
|
+
retrieve_attribute_collection
|
21
|
+
end
|
22
|
+
|
23
|
+
def attributes
|
24
|
+
instance_variables.select { |i| i != :@data }.map { |i| i.to_s[1..-1] }
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_s
|
28
|
+
"#{@name} - #{email}"
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def retrieve_attribute
|
34
|
+
%w(email name first_name last_name friendly_name location headline).each do |attr|
|
35
|
+
instance_variable_set("@#{attr}", @data['contact'][attr])
|
36
|
+
end
|
37
|
+
@success = @data['success']
|
38
|
+
end
|
39
|
+
|
40
|
+
def retrieve_attribute_collection
|
41
|
+
%w(occupations memberships images phones).each do |collection|
|
42
|
+
build_collection_instance_variable(collection)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def build_collection_instance_variable(collection)
|
47
|
+
collection_array = @data['contact'][collection].reduce([]) do |col, c|
|
48
|
+
col << find_klass_name(collection).new(c)
|
49
|
+
end
|
50
|
+
|
51
|
+
instance_variable_set("@#{collection}", collection_array)
|
52
|
+
end
|
53
|
+
|
54
|
+
def find_klass_name(collection)
|
55
|
+
PossibleEmail.const_get collection[0..-2].capitalize # 'images' => 'Image'
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'possible_email/response'
|
2
|
+
require 'possible_email/response_getter'
|
3
|
+
require 'possible_email/profile/profile'
|
4
|
+
|
5
|
+
module PossibleEmail
|
6
|
+
class RapportiveRequester
|
7
|
+
def self.request(*emails)
|
8
|
+
new(emails).request
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(emails)
|
12
|
+
@emails = emails.flatten
|
13
|
+
end
|
14
|
+
|
15
|
+
def request
|
16
|
+
email_profiles = accumulate_email_profiles
|
17
|
+
|
18
|
+
puts # newline after ...F... HACK
|
19
|
+
|
20
|
+
response = Response.new(email_profiles)
|
21
|
+
response.size == 1 ? response.first : response
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
# HACK
|
27
|
+
def accumulate_email_profiles
|
28
|
+
@emails.reduce([]) do |profiles, email|
|
29
|
+
profile = create_profile_for_email(email)
|
30
|
+
print profile ? '.' : 'F'
|
31
|
+
profiles << profile
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def create_profile_for_email(email)
|
36
|
+
session_token = ResponseGetter.create_session_token(email)
|
37
|
+
return unless session_token
|
38
|
+
|
39
|
+
response = ResponseGetter.retrieve_email_profile_using_session_token(email, session_token)
|
40
|
+
return unless response
|
41
|
+
|
42
|
+
Profile.new(response) if useful_response?(response)
|
43
|
+
end
|
44
|
+
|
45
|
+
def useful_response?(response)
|
46
|
+
response['success'] != 'nothing_useful'
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module PossibleEmail
|
2
|
+
class Response
|
3
|
+
include Enumerable
|
4
|
+
|
5
|
+
def initialize(profiles)
|
6
|
+
# remove nils created by ResponseGetter
|
7
|
+
@profiles = profiles.compact
|
8
|
+
end
|
9
|
+
|
10
|
+
def each(&block)
|
11
|
+
@profiles.each(&block)
|
12
|
+
end
|
13
|
+
|
14
|
+
def [](*args)
|
15
|
+
@profiles[*args]
|
16
|
+
end
|
17
|
+
|
18
|
+
def method_missing(method, *)
|
19
|
+
respond_to?(method) ? @profiles.send(method) : super
|
20
|
+
end
|
21
|
+
|
22
|
+
def respond_to?(method, *)
|
23
|
+
@profiles.respond_to?(method)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
STATUS_URL = 'https://rapportive.com/login_status?user_email='
|
2
|
+
PROFILE_URL = 'https://profiles.rapportive.com/contacts/email/'
|
3
|
+
|
4
|
+
module PossibleEmail
|
5
|
+
class ResponseGetter
|
6
|
+
class << self
|
7
|
+
def create_session_token(email)
|
8
|
+
status_url = STATUS_URL + email
|
9
|
+
response = request_url status_url
|
10
|
+
|
11
|
+
valid_response?(response) ? response['session_token'] : nil
|
12
|
+
end
|
13
|
+
|
14
|
+
def retrieve_email_profile_using_session_token(email, session_token)
|
15
|
+
profile_url = PROFILE_URL + email
|
16
|
+
header = { 'X-Session-Token' => session_token }
|
17
|
+
response = request_url profile_url, header
|
18
|
+
|
19
|
+
response.nil? ? nil : response
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def request_url(url, header = {})
|
25
|
+
request = HTTPI::Request.new
|
26
|
+
request.url = url
|
27
|
+
request.headers = header
|
28
|
+
|
29
|
+
JSON.parse(HTTPI.get(request).body)
|
30
|
+
end
|
31
|
+
|
32
|
+
def valid_response?(response)
|
33
|
+
response['error'].nil? && response['status'] == 200
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'possible_email/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "possible_email"
|
8
|
+
spec.version = PossibleEmail::VERSION
|
9
|
+
spec.authors = ["Patrick Perey"]
|
10
|
+
spec.email = ["the4dpatrick@yahoo.com"]
|
11
|
+
spec.summary = %q{Find anyone's email using their first name, last name, and domain.}
|
12
|
+
spec.description = %q{Find anyone's email using their first name, last name, and domain.}
|
13
|
+
spec.homepage = "https://github.com/the4dpatrick/possible-email"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_dependency 'httpi', '~> 2.2', '>= 2.2.5'
|
22
|
+
spec.add_dependency 'thor', '~> 0.19', '>= 0.19.1'
|
23
|
+
spec.add_dependency 'rubyntlm', '~> 0.3.4'
|
24
|
+
|
25
|
+
spec.add_development_dependency 'bundler', "~> 1.5"
|
26
|
+
spec.add_development_dependency 'rake'
|
27
|
+
spec.add_development_dependency 'rspec', '~> 3.0.0'
|
28
|
+
spec.add_development_dependency 'guard'
|
29
|
+
spec.add_development_dependency 'guard-rspec'
|
30
|
+
spec.add_development_dependency 'guard-bundler'
|
31
|
+
spec.add_development_dependency 'rb-fsevent'
|
32
|
+
spec.add_development_dependency 'growl'
|
33
|
+
spec.add_development_dependency 'coveralls'
|
34
|
+
|
35
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe PossibleEmail::Permutator do
|
4
|
+
|
5
|
+
describe '.call' do
|
6
|
+
|
7
|
+
context 'when one domain is given' do
|
8
|
+
let(:permutations) { PossibleEmail::Permutator.call('kevin', 'rose', 'gmail.com') }
|
9
|
+
|
10
|
+
it 'returns an array' do
|
11
|
+
expect(permutations).to be_instance_of(Array)
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'returns an array equal in size to every pattern in PATTERNS' do
|
15
|
+
expect(permutations.size).to eq(PATTERNS.split.size)
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'replaces all placeholder patterns' do
|
19
|
+
expect(permutations.to_s).not_to match(/[\{\}]+/)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
context 'when multiple domains are given' do
|
24
|
+
|
25
|
+
context 'as splat String arguments' do
|
26
|
+
let(:permutations) { PossibleEmail::Permutator.call('kevin', 'rose', 'gmail.com', 'yahoo.com') }
|
27
|
+
|
28
|
+
it 'returns an array' do
|
29
|
+
expect(permutations).to be_instance_of(Array)
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'returns an array equal in size to all permutations created with PATTERNS' do
|
33
|
+
expect(permutations.size).to eq(PATTERNS.split.size * 2) # two domains
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'replaces all placeholder patterns' do
|
37
|
+
expect(permutations.to_s).not_to match(/[\{\}]+/)
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'has the correct number of `yahoo.com` emails' do
|
41
|
+
yahoo_email_count = permutations.to_s.scan(/yahoo\.com/).size
|
42
|
+
|
43
|
+
expect(yahoo_email_count).to eq(PATTERNS.split.size)
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'has the correct number of `gmail.com` emails' do
|
47
|
+
gmail_email_count = permutations.to_s.scan(/gmail\.com/).size
|
48
|
+
|
49
|
+
expect(gmail_email_count).to eq(PATTERNS.split.size)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
context 'as an Array' do
|
54
|
+
let(:domains) { ['gmail.com', 'yahoo.com'] }
|
55
|
+
let(:permutations) { PossibleEmail::Permutator.call('kevin', 'rose', domains) }
|
56
|
+
|
57
|
+
it 'returns an array' do
|
58
|
+
expect(permutations).to be_instance_of(Array)
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'returns an array equal in size to every pattern in PATTERNS' do
|
62
|
+
expect(permutations.size).to eq(PATTERNS.split.size * domains.size) # two domains
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'replaces all placeholder patterns' do
|
66
|
+
expect(permutations.to_s).not_to match(/[\{\}]+/)
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'has the correct number of `yahoo.com` emails' do
|
70
|
+
yahoo_email_count = permutations.to_s.scan(/yahoo\.com/).size
|
71
|
+
|
72
|
+
expect(yahoo_email_count).to eq(PATTERNS.split.size)
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'has the correct number of `gmail.com` emails' do
|
76
|
+
gmail_email_count = permutations.to_s.scan(/gmail\.com/).size
|
77
|
+
|
78
|
+
expect(gmail_email_count).to eq(PATTERNS.split.size)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe PossibleEmail::Image do
|
4
|
+
let(:image) do PossibleEmail::Image.new('url' => 'image_url',
|
5
|
+
'service' => 'Facebook',
|
6
|
+
'url_proxied' => 'url_proxied')
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'returns the associated url' do
|
10
|
+
expect(image.url).to eq('image_url')
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'returns the associated service' do
|
14
|
+
expect(image.service).to eq('Facebook')
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'returns the associated url_proxied' do
|
18
|
+
expect(image.url_proxied).to eq('url_proxied')
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe PossibleEmail::Membership do
|
4
|
+
let(:membership) do
|
5
|
+
PossibleEmail::Membership.new('profile_url' => 'http://www.facebook.com/profile',
|
6
|
+
'profile_id' => '1234',
|
7
|
+
'username' => 'profile',
|
8
|
+
'site_name' => 'Facebook')
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'returns the associated profile_url' do
|
12
|
+
expect(membership.profile_url).to eq('http://www.facebook.com/profile')
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'returns the associated profile_id' do
|
16
|
+
expect(membership.profile_id).to eq('1234')
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'returns the associated username' do
|
20
|
+
expect(membership.username).to eq('profile')
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'returns the associated site_name' do
|
24
|
+
expect(membership.site_name).to eq('Facebook')
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe PossibleEmail::Occupation do
|
4
|
+
let(:occupation) do
|
5
|
+
PossibleEmail::Occupation.new('job_title' => 'entrepreneur', 'company' => 'startup')
|
6
|
+
end
|
7
|
+
|
8
|
+
it 'returns the associated job_title' do
|
9
|
+
expect(occupation.job_title).to eq('entrepreneur')
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'returns the associated company' do
|
13
|
+
expect(occupation.company).to eq('startup')
|
14
|
+
end
|
15
|
+
end
|
File without changes
|
@@ -0,0 +1,100 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe PossibleEmail::Profile do
|
4
|
+
let(:data) { JSON.parse(File.read(File.expand_path('spec/fixtures/rapportive_example_data.json'))) }
|
5
|
+
let(:profile) { PossibleEmail::Profile.new(data) }
|
6
|
+
|
7
|
+
it 'returns an error if no data is given' do
|
8
|
+
expect { PossibleEmail::Profile.new({}) }.to raise_error(PossibleEmail::NoDataError)
|
9
|
+
end
|
10
|
+
|
11
|
+
describe '#attributes' do
|
12
|
+
it 'returns a list of available' do
|
13
|
+
expect(profile.attributes).to eq(["email", "name", "first_name", "last_name", "friendly_name", "location", "headline", "success", "occupations", "memberships", "images", "phones"])
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
describe '#to_s' do
|
18
|
+
it 'returns formatted name and email' do
|
19
|
+
expect(profile.to_s).to eq('Kevin Rose - kevinrose@gmail.com')
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe 'Profile Attributes' do
|
24
|
+
it 'returns the associated email address' do
|
25
|
+
expect(profile.data).to be_instance_of(Hash)
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'returns the associated email address' do
|
29
|
+
expect(profile.email).to eq('kevinrose@gmail.com')
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'returns the associated name' do
|
33
|
+
expect(profile.name).to eq('Kevin Rose')
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'returns the associated first name' do
|
37
|
+
expect(profile.first_name).to eq('Kevin')
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'returns the associated last name' do
|
41
|
+
expect(profile.last_name).to eq('Rose')
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'returns the associated friendly_name' do
|
45
|
+
expect(profile.friendly_name).to eq('Kevin')
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'returns the associated locaiton' do
|
49
|
+
expect(profile.location).to eq('San Francisco Bay Area')
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'returns the associated headline' do
|
53
|
+
expect(profile.headline).to eq('General Partner at Google Ventures')
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'returns the type of success' do
|
57
|
+
expect(profile.success).to eq('image_and_occupation_and_useful_membership')
|
58
|
+
end
|
59
|
+
|
60
|
+
describe 'occupations' do
|
61
|
+
it 'returns an array with the correct number of occupations' do
|
62
|
+
expect(profile.occupations.size).to eq(2)
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'returns an array of Occupation instances' do
|
66
|
+
expect(profile.occupations).to all be_instance_of(PossibleEmail::Occupation)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
describe 'images' do
|
71
|
+
it 'returns an array with the correct number of images' do
|
72
|
+
expect(profile.images.size).to eq(12)
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'returns an array of Image instances' do
|
76
|
+
expect(profile.images).to all be_instance_of(PossibleEmail::Image)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
describe 'memberships' do
|
81
|
+
it 'returns an array with the correct number of memberships' do
|
82
|
+
expect(profile.memberships.size).to eq(18)
|
83
|
+
end
|
84
|
+
|
85
|
+
it 'returns an array of Memberships instances' do
|
86
|
+
expect(profile.memberships).to all be_instance_of(PossibleEmail::Membership)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# describe 'phones' do
|
91
|
+
# it 'returns an array with the correct number of memberships' do
|
92
|
+
# expect(profile.phones.size).to eq(0)
|
93
|
+
# end
|
94
|
+
|
95
|
+
# it 'returns an array of Memberships instances' do
|
96
|
+
# expect(profile.phones).to all be_instance_of(PossibleEmail::Phone)
|
97
|
+
# end
|
98
|
+
# end
|
99
|
+
end
|
100
|
+
end
|