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.
Files changed (39) hide show
  1. checksums.yaml +7 -0
  2. data/.coveralls.yml +1 -0
  3. data/.gitignore +17 -0
  4. data/.rspec +3 -0
  5. data/.travis.yml +6 -0
  6. data/Gemfile +2 -0
  7. data/Guardfile +11 -0
  8. data/LICENSE.txt +22 -0
  9. data/README.md +161 -0
  10. data/Rakefile +13 -0
  11. data/bin/possible_email +6 -0
  12. data/lib/possible_email.rb +56 -0
  13. data/lib/possible_email/cli.rb +33 -0
  14. data/lib/possible_email/error.rb +3 -0
  15. data/lib/possible_email/patterns.rb +38 -0
  16. data/lib/possible_email/permutator.rb +64 -0
  17. data/lib/possible_email/profile/image.rb +11 -0
  18. data/lib/possible_email/profile/membership.rb +12 -0
  19. data/lib/possible_email/profile/occupation.rb +10 -0
  20. data/lib/possible_email/profile/phone.rb +5 -0
  21. data/lib/possible_email/profile/profile.rb +58 -0
  22. data/lib/possible_email/rapportive_requester.rb +49 -0
  23. data/lib/possible_email/response.rb +26 -0
  24. data/lib/possible_email/response_getter.rb +37 -0
  25. data/lib/possible_email/version.rb +3 -0
  26. data/possible_email.gemspec +35 -0
  27. data/spec/PossibleEmail/possible_email/permutator_spec.rb +83 -0
  28. data/spec/PossibleEmail/possible_email/profile/image_spec.rb +20 -0
  29. data/spec/PossibleEmail/possible_email/profile/membership_spec.rb +26 -0
  30. data/spec/PossibleEmail/possible_email/profile/occupation_spec.rb +15 -0
  31. data/spec/PossibleEmail/possible_email/profile/phone_spec.rb +0 -0
  32. data/spec/PossibleEmail/possible_email/profile/profile_spec.rb +100 -0
  33. data/spec/PossibleEmail/possible_email/rapportive_requester_spec.rb +67 -0
  34. data/spec/PossibleEmail/possible_email/response_getter_spec.rb +50 -0
  35. data/spec/PossibleEmail/possible_email/response_spec.rb +22 -0
  36. data/spec/PossibleEmail/possible_email_spec.rb +92 -0
  37. data/spec/fixtures/rapportive_example_data.json +509 -0
  38. data/spec/spec_helper.rb +12 -0
  39. metadata +274 -0
@@ -0,0 +1,11 @@
1
+ module PossibleEmail
2
+ class Image
3
+ attr_reader :url, :service, :url_proxied
4
+
5
+ def initialize(data)
6
+ @url = data['url']
7
+ @service = data['service']
8
+ @url_proxied = data['url_proxied']
9
+ end
10
+ end
11
+ end
@@ -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,10 @@
1
+ module PossibleEmail
2
+ class Occupation
3
+ attr_reader :job_title, :company
4
+
5
+ def initialize(data)
6
+ @job_title = data['job_title']
7
+ @company = data['company']
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,5 @@
1
+ module PossibleEmail
2
+ class Phone
3
+
4
+ end
5
+ 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,3 @@
1
+ module PossibleEmail
2
+ VERSION = "0.0.2"
3
+ 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
@@ -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