possible_email 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|