garlenko 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 +19 -0
- data/Gemfile +4 -0
- data/Guardfile +22 -0
- data/Rakefile +2 -0
- data/garlenko.gemspec +25 -0
- data/lib/garlenko/base.rb +29 -0
- data/lib/garlenko/gmail.rb +45 -0
- data/lib/garlenko/live.rb +76 -0
- data/lib/garlenko/version.rb +3 -0
- data/lib/garlenko.rb +6 -0
- data/spec/credentials.yml.example +3 -0
- data/spec/lib/garlenko_spec.rb +53 -0
- data/spec/spec_helper.rb +5 -0
- data/spec/support/helpers.rb +18 -0
- metadata +132 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Guardfile
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# A sample Guardfile
|
2
|
+
# More info at https://github.com/guard/guard#readme
|
3
|
+
|
4
|
+
guard 'rspec', :version => 2 do
|
5
|
+
watch(%r{^spec/.+_spec\.rb$})
|
6
|
+
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
|
7
|
+
watch('spec/spec_helper.rb') { "spec" }
|
8
|
+
|
9
|
+
# Rails example
|
10
|
+
watch(%r{^spec/.+_spec\.rb$})
|
11
|
+
watch(%r{^app/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
|
12
|
+
watch(%r{^app/(.*)(\.erb|\.haml)$}) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" }
|
13
|
+
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
|
14
|
+
watch(%r{^app/controllers/(.+)_(controller)\.rb$}) { |m| ["spec/routing/#{m[1]}_routing_spec.rb", "spec/#{m[2]}s/#{m[1]}_#{m[2]}_spec.rb", "spec/acceptance/#{m[1]}_spec.rb"] }
|
15
|
+
watch(%r{^spec/support/(.+)\.rb$}) { "spec" }
|
16
|
+
watch('spec/spec_helper.rb') { "spec" }
|
17
|
+
watch('config/routes.rb') { "spec/routing" }
|
18
|
+
watch('app/controllers/application_controller.rb') { "spec/controllers" }
|
19
|
+
# Capybara request specs
|
20
|
+
watch(%r{^app/views/(.+)/.*\.(erb|haml)$}) { |m| "spec/requests/#{m[1]}_spec.rb" }
|
21
|
+
end
|
22
|
+
|
data/Rakefile
ADDED
data/garlenko.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/garlenko/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Lucas Florio"]
|
6
|
+
gem.email = ["lucasefe@gmail.com"]
|
7
|
+
gem.description = %q{contacts importer from Live (Hotmail), Gmail, and
|
8
|
+
Yahoo, using mechanize and nokogiri. }
|
9
|
+
gem.summary = %q{contacts importer from Live (Hotmail), Gmail, and
|
10
|
+
Yahoo, using mechanize and nokogiri. }
|
11
|
+
gem.homepage = ""
|
12
|
+
|
13
|
+
gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
14
|
+
gem.files = `git ls-files`.split("\n")
|
15
|
+
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
16
|
+
gem.name = "garlenko"
|
17
|
+
gem.require_paths = ["lib"]
|
18
|
+
gem.version = Garlenko::VERSION
|
19
|
+
gem.add_dependency "mechanize"
|
20
|
+
gem.add_dependency "nokogiri"
|
21
|
+
gem.add_dependency "gdata_19", [">= 1.1.3"]
|
22
|
+
gem.add_development_dependency "rspec", [">= 2.0.0"]
|
23
|
+
gem.add_development_dependency "guard-rspec"
|
24
|
+
gem.add_development_dependency "growl"
|
25
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
|
2
|
+
module Garlenko
|
3
|
+
|
4
|
+
def self.new(kind, username, password)
|
5
|
+
case kind
|
6
|
+
when :live then Live.new(username, password)
|
7
|
+
when :gmail then Gmail.new(username, password)
|
8
|
+
else
|
9
|
+
raise "Invalid kind (#{kind})!"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
|
14
|
+
class Base
|
15
|
+
attr_accessor :username
|
16
|
+
attr_writer :password
|
17
|
+
|
18
|
+
def initialize(username, password)
|
19
|
+
@username = username
|
20
|
+
@password = password
|
21
|
+
@connected = false
|
22
|
+
end
|
23
|
+
|
24
|
+
def connected?
|
25
|
+
@connected
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'gdata'
|
2
|
+
|
3
|
+
module Garlenko
|
4
|
+
|
5
|
+
class Gmail < Base
|
6
|
+
|
7
|
+
CONTACTS_SCOPE = 'http://www.google.com/m8/feeds/'
|
8
|
+
CONTACTS_FEED = CONTACTS_SCOPE + 'contacts/default/full/?max-results=1000'
|
9
|
+
|
10
|
+
def contacts
|
11
|
+
connect!
|
12
|
+
feed = @client.get(CONTACTS_FEED).to_xml
|
13
|
+
|
14
|
+
@contacts = feed.elements.to_a('entry').collect do |entry|
|
15
|
+
title, email, photo = entry.elements['title'].text, nil, [nil, nil]
|
16
|
+
|
17
|
+
begin
|
18
|
+
entry.elements.each('link[@gd:etag]') do |e|
|
19
|
+
gdata_response = @client.get(e.attribute('href').value)
|
20
|
+
photo = [gdata_response.body, gdata_response.headers['content-type']] if gdata_response.status_code == 200 and !gdata_response.body.nil?
|
21
|
+
end
|
22
|
+
rescue StandardError
|
23
|
+
photo = [nil, nil]
|
24
|
+
end
|
25
|
+
|
26
|
+
entry.elements.each('gd:email') do |e|
|
27
|
+
email = e.attribute('address').value if e.attribute('primary')
|
28
|
+
end
|
29
|
+
{ :name => title, :email => email, :photo => photo } unless email.nil?
|
30
|
+
end
|
31
|
+
@contacts.compact!
|
32
|
+
@connected = true
|
33
|
+
return @contacts if @contacts
|
34
|
+
end
|
35
|
+
|
36
|
+
def connect!
|
37
|
+
@client = GData::Client::Contacts.new
|
38
|
+
@client.clientlogin(@username, @password, @captcha_token, @captcha_response)
|
39
|
+
@connected = true
|
40
|
+
rescue GData::Client::AuthorizationError => e
|
41
|
+
@connected = false
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module Garlenko
|
2
|
+
|
3
|
+
class Live < Base
|
4
|
+
|
5
|
+
attr_accessor :last_result
|
6
|
+
|
7
|
+
LOGIN_URL = "https://mid.live.com/si/login.aspx"
|
8
|
+
CONTACTS_URL = "http://mpeople.live.com"
|
9
|
+
|
10
|
+
def agent
|
11
|
+
@agent ||= Mechanize.new { |agent|
|
12
|
+
agent.user_agent = 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)'
|
13
|
+
agent.follow_meta_refresh = true
|
14
|
+
}
|
15
|
+
end
|
16
|
+
|
17
|
+
def connect!(force_connect = false)
|
18
|
+
return @connected unless !connected? or force_connect
|
19
|
+
login_form = agent.get(LOGIN_URL).form('EmailPasswordForm')
|
20
|
+
login_form.LoginTextBox = @username
|
21
|
+
login_form.PasswordTextBox = @password
|
22
|
+
login_res = agent.submit(login_form, login_form.submits.first)
|
23
|
+
@connected = !login_res.body.include?("NotificationContainerError")
|
24
|
+
end
|
25
|
+
|
26
|
+
def contacts
|
27
|
+
connect! unless connected?
|
28
|
+
pool = []
|
29
|
+
contacts_so_far = []
|
30
|
+
|
31
|
+
on_contacts_page CONTACTS_URL do |url, page|
|
32
|
+
pool << Thread.new(url, page) do |u, p|
|
33
|
+
fetch_contacts_from(p)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
contacts_so_far = pool.each(&:join).map(&:value).flatten
|
37
|
+
puts "fetched: #{contacts_so_far.size}"
|
38
|
+
contacts_so_far
|
39
|
+
end
|
40
|
+
|
41
|
+
def on_contacts_page(url, &block)
|
42
|
+
unless url.nil?
|
43
|
+
page = agent.get(url)
|
44
|
+
block.call(url, page)
|
45
|
+
next_path = next_page_link_for(page)
|
46
|
+
next_url = next_path.nil? ? nil : CONTACTS_URL + next_path.href
|
47
|
+
on_contacts_page(next_url, &block)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def next_page_link_for(page)
|
52
|
+
page.links_with(:id => /ContactList_next/).first
|
53
|
+
end
|
54
|
+
|
55
|
+
def fetch_contacts_from(page)
|
56
|
+
page.links_with(:href => /contactinfo.aspx/).map do |link|
|
57
|
+
fetch_contact_info_from link.href
|
58
|
+
end.flatten
|
59
|
+
end
|
60
|
+
|
61
|
+
def fetch_contact_info_from(path)
|
62
|
+
url = CONTACTS_URL + path
|
63
|
+
doc = Nokogiri::HTML(agent.get(url).body)
|
64
|
+
name = doc.css('#title').first.content
|
65
|
+
emails = doc.css("#elklid, #elkps, #elkot, #emtot, #elkim")
|
66
|
+
unless emails.empty?
|
67
|
+
emails.map do |email|
|
68
|
+
{ :name => name, :email => email.content }
|
69
|
+
end
|
70
|
+
else
|
71
|
+
[]
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
end
|
data/lib/garlenko.rb
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
shared_examples_for "any consumer" do
|
4
|
+
|
5
|
+
it "should login" do
|
6
|
+
garlenko.connect!
|
7
|
+
garlenko.should be_connected
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should NOT login with invalid data" do
|
11
|
+
garlenko.password = "INVALID"
|
12
|
+
garlenko.connect!
|
13
|
+
garlenko.should_not be_connected
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should fetch the contacts" do
|
17
|
+
contacts = garlenko.contacts
|
18
|
+
contacts.should be_a_kind_of(Array)
|
19
|
+
contacts.should_not be_empty
|
20
|
+
contact = contacts.first
|
21
|
+
contact[:email].should_not be_nil
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe Garlenko do
|
26
|
+
context "against Gmail" do
|
27
|
+
|
28
|
+
let(:garlenko) do
|
29
|
+
gmail = load_credentials.gmail
|
30
|
+
Garlenko.new :gmail, gmail["username"], gmail['password']
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should construct a valid garlenko consumer" do
|
34
|
+
garlenko.should be_a_kind_of(Garlenko::Gmail)
|
35
|
+
end
|
36
|
+
|
37
|
+
it_should_behave_like "any consumer"
|
38
|
+
end
|
39
|
+
|
40
|
+
context "against Live/Hotmail and the like" do
|
41
|
+
|
42
|
+
let(:garlenko) do
|
43
|
+
live = load_credentials.live
|
44
|
+
Garlenko.new :live, live["username"], live['password']
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should construct a valid garlenko consumer" do
|
48
|
+
garlenko.should be_a_kind_of(Garlenko::Live)
|
49
|
+
end
|
50
|
+
|
51
|
+
it_should_behave_like "any consumer"
|
52
|
+
end
|
53
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require "ostruct"
|
2
|
+
require "yaml"
|
3
|
+
|
4
|
+
module Helpers
|
5
|
+
|
6
|
+
def load_credentials
|
7
|
+
credentials_file = File.join(Dir.pwd, "/spec/credentials.yml")
|
8
|
+
if File.exists?(credentials_file)
|
9
|
+
conf = YAML.load(File.read("#{Dir.pwd}/spec/credentials.yml"))
|
10
|
+
OpenStruct.new(conf)
|
11
|
+
else
|
12
|
+
raise "missing credentials file. "
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
RSpec.configuration.include Helpers
|
metadata
ADDED
@@ -0,0 +1,132 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: garlenko
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Lucas Florio
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-12-15 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: mechanize
|
16
|
+
requirement: &70274506739960 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70274506739960
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: nokogiri
|
27
|
+
requirement: &70274506738660 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70274506738660
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: gdata_19
|
38
|
+
requirement: &70274506758120 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: 1.1.3
|
44
|
+
type: :runtime
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *70274506758120
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: rspec
|
49
|
+
requirement: &70274506768040 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 2.0.0
|
55
|
+
type: :development
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *70274506768040
|
58
|
+
- !ruby/object:Gem::Dependency
|
59
|
+
name: guard-rspec
|
60
|
+
requirement: &70274506766420 !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ! '>='
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: '0'
|
66
|
+
type: :development
|
67
|
+
prerelease: false
|
68
|
+
version_requirements: *70274506766420
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: growl
|
71
|
+
requirement: &70274506763520 !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
73
|
+
requirements:
|
74
|
+
- - ! '>='
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0'
|
77
|
+
type: :development
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: *70274506763520
|
80
|
+
description: ! "contacts importer from Live (Hotmail), Gmail, and\n Yahoo, using
|
81
|
+
mechanize and nokogiri. "
|
82
|
+
email:
|
83
|
+
- lucasefe@gmail.com
|
84
|
+
executables: []
|
85
|
+
extensions: []
|
86
|
+
extra_rdoc_files: []
|
87
|
+
files:
|
88
|
+
- .gitignore
|
89
|
+
- Gemfile
|
90
|
+
- Guardfile
|
91
|
+
- Rakefile
|
92
|
+
- garlenko.gemspec
|
93
|
+
- lib/garlenko.rb
|
94
|
+
- lib/garlenko/base.rb
|
95
|
+
- lib/garlenko/gmail.rb
|
96
|
+
- lib/garlenko/live.rb
|
97
|
+
- lib/garlenko/version.rb
|
98
|
+
- spec/credentials.yml.example
|
99
|
+
- spec/lib/garlenko_spec.rb
|
100
|
+
- spec/spec_helper.rb
|
101
|
+
- spec/support/helpers.rb
|
102
|
+
homepage: ''
|
103
|
+
licenses: []
|
104
|
+
post_install_message:
|
105
|
+
rdoc_options: []
|
106
|
+
require_paths:
|
107
|
+
- lib
|
108
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
109
|
+
none: false
|
110
|
+
requirements:
|
111
|
+
- - ! '>='
|
112
|
+
- !ruby/object:Gem::Version
|
113
|
+
version: '0'
|
114
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
115
|
+
none: false
|
116
|
+
requirements:
|
117
|
+
- - ! '>='
|
118
|
+
- !ruby/object:Gem::Version
|
119
|
+
version: '0'
|
120
|
+
requirements: []
|
121
|
+
rubyforge_project:
|
122
|
+
rubygems_version: 1.8.10
|
123
|
+
signing_key:
|
124
|
+
specification_version: 3
|
125
|
+
summary: contacts importer from Live (Hotmail), Gmail, and Yahoo, using mechanize
|
126
|
+
and nokogiri.
|
127
|
+
test_files:
|
128
|
+
- spec/credentials.yml.example
|
129
|
+
- spec/lib/garlenko_spec.rb
|
130
|
+
- spec/spec_helper.rb
|
131
|
+
- spec/support/helpers.rb
|
132
|
+
has_rdoc:
|