Repository: cardmagic/contacts
Branch: master
Commit: e706105e24f0
Files: 28
Total size: 43.7 KB
Directory structure:
gitextract_qyvd4b8y/
├── .gitignore
├── .travis.yml
├── Gemfile
├── LICENSE
├── README.md
├── Rakefile
├── contacts.gemspec
├── cruise_config.rb
├── examples/
│ └── grab_contacts.rb
├── geminstaller.yml
├── lib/
│ ├── contacts/
│ │ ├── aol.rb
│ │ ├── base.rb
│ │ ├── gmail.rb
│ │ ├── hotmail.rb
│ │ ├── json_picker.rb
│ │ ├── mailru.rb
│ │ ├── plaxo.rb
│ │ └── yahoo.rb
│ └── contacts.rb
└── test/
├── example_accounts.yml
├── test_helper.rb
├── test_suite.rb
└── unit/
├── aol_contact_importer_test.rb
├── gmail_contact_importer_test.rb
├── hotmail_contact_importer_test.rb
├── mailru_contact_importer_test.rb
├── test_accounts_test.rb
└── yahoo_csv_contact_importer_test.rb
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
.DS_Store
pkg/*
test/accounts.yml
nbproject/
================================================
FILE: .travis.yml
================================================
language: ruby
rvm:
- 1.9.3
- 1.9.2
- jruby-18mode
- jruby-19mode
- rbx-18mode
- rbx-19mode
- ruby-head
- jruby-head
- 1.8.7
- ree
================================================
FILE: Gemfile
================================================
source 'https://rubygems.org'
gem 'json', ">= 1.1.1"
gem 'gdata_19', '1.1.5'
gem 'rspec', :require => 'spec'
gem 'rake'
gem 'hpricot'
================================================
FILE: LICENSE
================================================
Copyright (c) 2006, Lucas Carlson, MOG
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
Neither the name of the Lucas Carlson nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
================================================
FILE: README.md
================================================
Welcome to Contacts
===================
Contacts is a universal interface to grab contact list information from various providers including Hotmail, AOL, Gmail, Plaxo and Yahoo.
Download
--------
* gem install contacts
* http://github.com/cardmagic/contacts
* git clone git://github.com/cardmagic/contacts.git
Background
----------
For a long time, the only way to get a list of contacts from your free online email accounts was with proprietary PHP scripts that would cost you $50. The act of grabbing that list is a simple matter of screen scrapping and this library gives you all the functionality you need. Thanks to the generosity of the highly popular Rails website MOG (http://mog.com) for allowing this library to be released open-source to the world. It is easy to extend this library to add new free email providers, so please contact the author if you would like to help.
Usage
-----
<pre>
<code>
Contacts::Hotmail.new(login, password).contacts # => [["name", "foo@bar.com"], ["another name", "bow@wow.com"]]
Contacts::Yahoo.new(login, password).contacts
Contacts::Gmail.new(login, password).contacts
Contacts.new(:gmail, login, password).contacts
Contacts.new(:hotmail, login, password).contacts
Contacts.new(:yahoo, login, password).contacts
Contacts.guess(login, password).contacts
</code>
</pre>
Notice there are three ways to use this library so that you can limit the use as much as you would like in your particular application. The Contacts.guess method will automatically concatenate all the address book contacts from each of the successful logins in the case that a username password works across multiple services.
Captcha error
-------------
If there are too many failed attempts with the gmail login info, Google will raise a captcha response. To integrate the captcha handling, pass in the token and response via:
<pre><code>
Contacts::Gmail.new(login, password, :captcha_token => params[:captcha_token], :captcha_response => params[:captcha_response]).contacts
</code></pre>
Examples
--------
See the examples/ directory.
Authors
-------
> Lucas Carlson from MOG (mailto:lucas@rufy.com) - http://mog.com
Contributors
------------
* Britt Selvitelle from Twitter (mailto:anotherbritt@gmail.com) - http://twitter.com
* Tony Targonski from GigPark (mailto:tony@gigpark.com) - http://gigpark.com
* Waheed Barghouthi from Watwet (mailto:waheed.barghouthi@gmail.com) - http://watwet.com
* Glenn Sidney from Glenn Fu (mailto:glenn@glennfu.com) - http://glennfu.com
* Brian McQuay from Onomojo (mailto:brian@onomojo.com) - http://onomojo.com
* Adam Hunter (mailto:adamhunter@me.com) - http://adamhunter.me/
* Glenn Ford (mailto:glenn@glennfu.com) - http://www.glennfu.com/
* Leonardo Wong (mailto:mac@boy.name)
* Rusty Burchfield
* justintv
This library is released under the terms of the BSD.
================================================
FILE: Rakefile
================================================
require 'rubygems'
require 'bundler/setup'
require 'rake'
require 'rake/testtask'
require 'rake/rdoctask'
require 'rake/gempackagetask'
require 'rake/contrib/rubyforgepublisher'
require './lib/contacts'
PKG_VERSION = Contacts::VERSION
PKG_FILES = FileList[
"lib/**/*", "bin/*", "test/**/*", "[A-Z]*", "Rakefile", "doc/**/*", "examples/**/*"
] - ["test/accounts.yml"]
desc "Default Task"
task :default => [ :test ]
# Run the unit tests
desc "Run all unit tests"
Rake::TestTask.new("test") { |t|
t.libs << "lib"
t.pattern = 'test/*/*_test.rb'
t.verbose = true
}
# Make a console, useful when working on tests
desc "Generate a test console"
task :console do
verbose( false ) { sh "irb -I lib/ -r 'contacts'" }
end
# Genereate the RDoc documentation
desc "Create documentation"
Rake::RDocTask.new("doc") { |rdoc|
rdoc.title = "Contact List - ridiculously easy contact list information from various providers including Yahoo, Gmail, and Hotmail"
rdoc.rdoc_dir = 'doc'
rdoc.rdoc_files.include('README')
rdoc.rdoc_files.include('lib/**/*.rb')
}
# Genereate the package
spec = Gem::Specification.new do |s|
#### Basic information.
s.name = 'adamhunter-contacts'
s.version = PKG_VERSION
s.summary = <<-EOF
Ridiculously easy contact list information from various providers including Yahoo, Gmail, and Hotmail
EOF
s.description = <<-EOF
Ridiculously easy contact list information from various providers including Yahoo, Gmail, and Hotmail
EOF
#### Which files are to be included in this gem? Everything! (Except CVS directories.)
s.files = PKG_FILES
#### Load-time details: library and application (you will need one or both).
s.require_path = 'lib'
s.autorequire = 'contacts'
s.add_dependency('json', '>= 0.4.1')
s.add_dependency('gdata', '= 1.1.1')
s.requirements << "A json parser, the gdata ruby gem"
#### Documentation and testing.
s.has_rdoc = true
#### Author and project details.
s.author = "Lucas Carlson"
s.email = "lucas@rufy.com"
s.homepage = "http://rubyforge.org/projects/contacts"
end
Rake::GemPackageTask.new(spec) do |pkg|
pkg.need_zip = true
pkg.need_tar = true
end
desc "Report code statistics (KLOCs, etc) from the application"
task :stats do
require 'code_statistics'
CodeStatistics.new(
["Library", "lib"],
["Units", "test"]
).to_s
end
================================================
FILE: contacts.gemspec
================================================
Gem::Specification.new do |s|
s.name = "contacts"
s.version = "1.2.4"
s.date = "2010-07-06"
s.summary = "A universal interface to grab contact list information from various providers including Yahoo, AOL, Gmail, Hotmail, and Plaxo."
s.email = "lucas@rufy.com"
s.homepage = "http://github.com/cardmagic/contacts"
s.description = "A universal interface to grab contact list information from various providers including Yahoo, AOL, Gmail, Hotmail, and Plaxo."
s.has_rdoc = false
s.authors = ["Lucas Carlson"]
s.files = ["LICENSE", "Rakefile", "README", "examples/grab_contacts.rb", "lib/contacts.rb", "lib/contacts/base.rb", "lib/contacts/json_picker.rb", "lib/contacts/gmail.rb", "lib/contacts/aol.rb", "lib/contacts/hotmail.rb", "lib/contacts/plaxo.rb", "lib/contacts/yahoo.rb"]
s.add_dependency("json", ">= 1.1.1")
s.add_dependency('gdata', '1.1.2')
end
================================================
FILE: cruise_config.rb
================================================
# Project-specific configuration for CruiseControl.rb
Project.configure do |project|
# Send email notifications about broken and fixed builds to email1@your.site, email2@your.site (default: send to nobody)
# if building this on your own CI box, please remove!
project.email_notifier.emails = ['opensource@pivotallabs.com']
# Set email 'from' field to john@doe.com:
# project.email_notifier.from = 'john@doe.com'
# Build the project by invoking rake task 'custom'
# project.rake_task = 'custom'
# Build the project by invoking shell script "build_my_app.sh". Keep in mind that when the script is invoked, current working directory is
# [cruise]/projects/your_project/work, so if you do not keep build_my_app.sh in version control, it should be '../build_my_app.sh' instead
# project.build_command = 'build_my_app.sh'
# Ping Subversion for new revisions every 5 minutes (default: 30 seconds)
# project.scheduler.polling_interval = 5.minutes
end
================================================
FILE: examples/grab_contacts.rb
================================================
require File.dirname(__FILE__)+"/../lib/contacts"
login = ARGV[0]
password = ARGV[1]
Contacts::Gmail.new(login, password).contacts
Contacts.new(:gmail, login, password).contacts
Contacts.new("gmail", login, password).contacts
Contacts.guess(login, password).contacts
================================================
FILE: geminstaller.yml
================================================
---
defaults:
install_options: --no-ri --no-rdoc
gems:
- name: json
version: >= 1.1.1
- name: gdata
version: >= 1.1.1
================================================
FILE: lib/contacts/aol.rb
================================================
class Contacts
require 'hpricot'
require 'csv'
class Aol < Base
URL = "http://www.aol.com/"
LOGIN_URL = "https://my.screenname.aol.com/_cqr/login/login.psp"
LOGIN_REFERER_URL = "http://webmail.aol.com/"
LOGIN_REFERER_PATH = "sitedomain=sns.webmail.aol.com&lang=en&locale=us&authLev=0&uitype=mini&loginId=&redirType=js&xchk=false"
AOL_NUM = "29970-343" # this seems to change each time they change the protocol
CONTACT_LIST_URL = "http://webmail.aol.com/#{AOL_NUM}/aim-2/en-us/Lite/ContactList.aspx?folder=Inbox&showUserFolders=False"
CONTACT_LIST_CSV_URL = "http://webmail.aol.com/#{AOL_NUM}/aim-2/en-us/Lite/ABExport.aspx?command=all"
PROTOCOL_ERROR = "AOL has changed its protocols, please upgrade this library first. If that does not work, dive into the code and submit a patch at http://github.com/cardmagic/contacts"
def real_connect
if login.strip =~ /^(.+)@aol\.com$/ # strip off the @aol.com for AOL logins
login = $1
end
postdata = {
"loginId" => login,
"password" => password,
"rememberMe" => "on",
"_sns_fg_color_" => "",
"_sns_err_color_" => "",
"_sns_link_color_" => "",
"_sns_width_" => "",
"_sns_height_" => "",
"offerId" => "mail-second-en-us",
"_sns_bg_color_" => "",
"sitedomain" => "sns.webmail.aol.com",
"regPromoCode" => "",
"mcState" => "initialized",
"uitype" => "std",
"siteId" => "",
"lang" => "en",
"locale" => "us",
"authLev" => "0",
"siteState" => "",
"isSiteStateEncoded" => "false",
"use_aam" => "0",
"seamless" => "novl",
"aolsubmit" => CGI.escape("Sign In"),
"idType" => "SN",
"usrd" => "",
"doSSL" => "",
"redirType" => "",
"xchk" => "false"
}
# Get this cookie and stick it in the form to confirm to Aol that your cookies work
data, resp, cookies, forward = get(URL)
postdata["stips"] = cookie_hash_from_string(cookies)["stips"]
postdata["tst"] = cookie_hash_from_string(cookies)["tst"]
data, resp, cookies, forward, old_url = get(LOGIN_REFERER_URL, cookies) + [URL]
until forward.nil?
data, resp, cookies, forward, old_url = get(forward, cookies, old_url) + [forward]
end
data, resp, cookies, forward, old_url = get("#{LOGIN_URL}?#{LOGIN_REFERER_PATH}", cookies) + [LOGIN_REFERER_URL]
until forward.nil?
data, resp, cookies, forward, old_url = get(forward, cookies, old_url) + [forward]
end
doc = Hpricot(data)
(doc/:input).each do |input|
postdata["usrd"] = input.attributes["value"] if input.attributes["name"] == "usrd"
end
# parse data for <input name="usrd" value="2726212" type="hidden"> and add it to the postdata
postdata["SNS_SC"] = cookie_hash_from_string(cookies)["SNS_SC"]
postdata["SNS_LDC"] = cookie_hash_from_string(cookies)["SNS_LDC"]
postdata["LTState"] = cookie_hash_from_string(cookies)["LTState"]
# raise data.inspect
data, resp, cookies, forward, old_url = post(LOGIN_URL, h_to_query_string(postdata), cookies, LOGIN_REFERER_URL) + [LOGIN_REFERER_URL]
until forward.nil?
data, resp, cookies, forward, old_url = get(forward, cookies, old_url) + [forward]
end
if data.index("Invalid Username or Password. Please try again.")
raise AuthenticationError, "Username and password do not match"
elsif data.index("Required field must not be blank")
raise AuthenticationError, "Login and password must not be blank"
elsif data.index("errormsg_0_logincaptcha")
raise AuthenticationError, "Captcha error"
elsif data.index("Invalid request")
raise ConnectionError, PROTOCOL_ERROR
elsif cookies == ""
raise ConnectionError, PROTOCOL_ERROR
end
@cookies = cookies
end
def contacts
postdata = {
"file" => 'contacts',
"fileType" => 'csv'
}
return @contacts if @contacts
if connected?
data, resp, cookies, forward, old_url = get(CONTACT_LIST_URL, @cookies, CONTACT_LIST_URL) + [CONTACT_LIST_URL]
until forward.nil?
data, resp, cookies, forward, old_url = get(forward, cookies, old_url) + [forward]
end
if resp.code_type != Net::HTTPOK
raise ConnectionError, self.class.const_get(:PROTOCOL_ERROR)
end
# parse data and grab <input name="user" value="8QzMPIAKs2" type="hidden">
doc = Hpricot(data)
(doc/:input).each do |input|
postdata["user"] = input.attributes["value"] if input.attributes["name"] == "user"
end
data, resp, cookies, forward, old_url = get(CONTACT_LIST_CSV_URL, @cookies, CONTACT_LIST_URL) + [CONTACT_LIST_URL]
until forward.nil?
data, resp, cookies, forward, old_url = get(forward, cookies, old_url) + [forward]
end
if data.include?("error.gif")
raise AuthenticationError, "Account invalid"
end
parse data
end
end
private
def parse(data, options={})
data = CSV::Reader.parse(data)
col_names = data.shift
@contacts = data.map do |person|
["#{person[0]} #{person[1]}", person[4]] if person[4] && !person[4].empty?
end.compact
end
def h_to_query_string(hash)
u = ERB::Util.method(:u)
hash.map { |k, v|
u.call(k) + "=" + u.call(v)
}.join("&")
end
end
TYPES[:aol] = Aol
end
================================================
FILE: lib/contacts/base.rb
================================================
require "cgi"
require "net/http"
require "net/https"
require "uri"
require "zlib"
require "stringio"
require "thread"
require "erb"
class Contacts
TYPES = {}
VERSION = "1.2.4"
class Base
def initialize(login, password, options={})
@login = login
@password = password
@captcha_token = options[:captcha_token]
@captcha_response = options[:captcha_response]
@connections = {}
connect
end
def connect
raise AuthenticationError, "Login and password must not be nil, login: #{@login.inspect}, password: #{@password.inspect}" if @login.nil? || @login.empty? || @password.nil? || @password.empty?
real_connect
end
def connected?
@cookies && !@cookies.empty?
end
def contacts(options = {})
return @contacts if @contacts
if connected?
url = URI.parse(contact_list_url)
http = open_http(url)
resp, data = http.get("#{url.path}?#{url.query}",
"Cookie" => @cookies
)
if resp.code_type != Net::HTTPOK
raise ConnectionError, self.class.const_get(:PROTOCOL_ERROR)
end
parse(data, options)
end
end
def login
@attempt ||= 0
@attempt += 1
if @attempt == 1
@login
else
if @login.include?("@#{domain}")
@login.sub("@#{domain}","")
else
"#{@login}@#{domain}"
end
end
end
def password
@password
end
def skip_gzip?
false
end
private
def domain
@d ||= URI.parse(self.class.const_get(:URL)).host.sub(/^www\./,'')
end
def contact_list_url
self.class.const_get(:CONTACT_LIST_URL)
end
def address_book_url
self.class.const_get(:ADDRESS_BOOK_URL)
end
def open_http(url)
c = @connections[Thread.current.object_id] ||= {}
http = c["#{url.host}:#{url.port}"]
unless http
http = Net::HTTP.new(url.host, url.port)
if url.port == 443
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
end
c["#{url.host}:#{url.port}"] = http
end
http.start unless http.started?
http
end
def cookie_hash_from_string(cookie_string)
cookie_string.split(";").map{|i|i.split("=", 2).map{|j|j.strip}}.inject({}){|h,i|h[i[0]]=i[1];h}
end
def parse_cookies(data, existing="")
return existing if data.nil?
cookies = cookie_hash_from_string(existing)
data.gsub!(/ ?[\w]+=EXPIRED;/,'')
data.gsub!(/ ?expires=(.*?, .*?)[;,$]/i, ';')
data.gsub!(/ ?(domain|path)=[\S]*?[;,$]/i,';')
data.gsub!(/[,;]?\s*(secure|httponly)/i,'')
data.gsub!(/(;\s*){2,}/,', ')
data.gsub!(/(,\s*){2,}/,', ')
data.sub!(/^,\s*/,'')
data.sub!(/\s*,$/,'')
data.split(", ").map{|t|t.to_s.split(";").first}.each do |data|
k, v = data.split("=", 2).map{|j|j.strip}
if cookies[k] && v.empty?
cookies.delete(k)
elsif v && !v.empty?
cookies[k] = v
end
end
cookies.map{|k,v| "#{k}=#{v}"}.join("; ")
end
def remove_cookie(cookie, cookies)
parse_cookies("#{cookie}=", cookies)
end
def post(url, postdata, cookies="", referer="")
url = URI.parse(url)
http = open_http(url)
http_header = { "User-Agent" => "Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en-US; rv:1.8.1) Gecko/20061010 Firefox/2.0",
"Accept-Encoding" => "gzip",
"Cookie" => cookies,
"Referer" => referer,
"Content-Type" => 'application/x-www-form-urlencoded'
}
http_header.reject!{|k, v| k == 'Accept-Encoding'} if skip_gzip?
resp, data = http.post(url.path, postdata, http_header)
data = uncompress(resp, data)
cookies = parse_cookies(resp.response['set-cookie'], cookies)
forward = resp.response['Location']
forward ||= (data =~ /<meta.*?url='([^']+)'/ ? CGI.unescapeHTML($1) : nil)
if (not forward.nil?) && URI.parse(forward).host.nil?
forward = url.scheme.to_s + "://" + url.host.to_s + forward
end
return data, resp, cookies, forward
end
def get(url, cookies="", referer="")
url = URI.parse(url)
attempt = 0
begin
http = open_http(url)
resp, data = http.get("#{url.path}?#{url.query}",
"User-Agent" => "Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en-US; rv:1.8.1) Gecko/20061010 Firefox/2.0",
"Accept-Encoding" => "gzip",
"Cookie" => cookies,
"Referer" => referer
)
rescue EOFError => err
attempt += 1
retry if attempt == 1
end
data = uncompress(resp, data)
cookies = parse_cookies(resp.response['set-cookie'], cookies)
forward = resp.response['Location']
if (not forward.nil?) && URI.parse(forward).host.nil?
forward = url.scheme.to_s + "://" + url.host.to_s + forward
end
return data, resp, cookies, forward
end
def uncompress(resp, data)
case resp.response['content-encoding']
when 'gzip'
gz = Zlib::GzipReader.new(StringIO.new(data))
data = gz.read
gz.close
resp.response['content-encoding'] = nil
# FIXME: Not sure what Hotmail was feeding me with their 'deflate',
# but the headers definitely were not right
when 'deflate'
data = Zlib::Inflate.inflate(data)
resp.response['content-encoding'] = nil
end
data
end
end
class ContactsError < StandardError
end
class AuthenticationError < ContactsError
end
class ConnectionError < ContactsError
end
class TypeNotFound < ContactsError
end
def self.new(type, login, password, options={})
if TYPES.include?(type.to_s.intern)
TYPES[type.to_s.intern].new(login, password, options)
else
raise TypeNotFound, "#{type.inspect} is not a valid type, please choose one of the following: #{TYPES.keys.inspect}"
end
end
def self.guess(login, password, options={})
TYPES.inject([]) do |a, t|
begin
a + t[1].new(login, password, options).contacts
rescue AuthenticationError
a
end
end.uniq
end
end
================================================
FILE: lib/contacts/gmail.rb
================================================
require 'gdata'
class Contacts
class Gmail < Base
CONTACTS_SCOPE = 'http://www.google.com/m8/feeds/'
CONTACTS_FEED = CONTACTS_SCOPE + 'contacts/default/full/?max-results=1000'
def contacts
return @contacts if @contacts
end
def real_connect
@client = GData::Client::Contacts.new
@client.clientlogin(@login, @password, @captcha_token, @captcha_response)
feed = @client.get(CONTACTS_FEED).to_xml
@contacts = feed.elements.to_a('entry').collect do |entry|
title, email = entry.elements['title'].text, nil
entry.elements.each('gd:email') do |e|
email = e.attribute('address').value if e.attribute('primary')
end
[title, email] unless email.nil?
end
@contacts.compact!
rescue GData::Client::AuthorizationError => e
raise AuthenticationError, "Username or password are incorrect"
end
private
TYPES[:gmail] = Gmail
end
end
================================================
FILE: lib/contacts/hotmail.rb
================================================
class Contacts
class Hotmail < Base
URL = "https://login.live.com/login.srf?id=2"
OLD_CONTACT_LIST_URL = "http://%s/cgi-bin/addresses"
NEW_CONTACT_LIST_URL = "http://%s/mail/GetContacts.aspx"
CONTACT_LIST_URL = "http://mpeople.live.com/default.aspx?pg=0"
COMPOSE_URL = "http://%s/cgi-bin/compose?"
PROTOCOL_ERROR = "Hotmail has changed its protocols, please upgrade this library first. If that does not work, report this error at http://rubyforge.org/forum/?group_id=2693"
PWDPAD = "IfYouAreReadingThisYouHaveTooMuchFreeTime"
MAX_HTTP_THREADS = 8
def real_connect
data, resp, cookies, forward = get(URL)
old_url = URL
until forward.nil?
data, resp, cookies, forward, old_url = get(forward, cookies, old_url) + [forward]
end
postdata = "PPSX=%s&PwdPad=%s&login=%s&passwd=%s&LoginOptions=2&PPFT=%s" % [
CGI.escape(data.split("><").grep(/PPSX/).first[/=\S+$/][2..-3]),
PWDPAD[0...(PWDPAD.length-@password.length)],
CGI.escape(login),
CGI.escape(password),
CGI.escape(data.split("><").grep(/PPFT/).first[/=\S+$/][2..-3])
]
form_url = data.split("><").grep(/form/).first.split[5][8..-2]
data, resp, cookies, forward = post(form_url, postdata, cookies)
old_url = form_url
until cookies =~ /; PPAuth=/ || forward.nil?
data, resp, cookies, forward, old_url = get(forward, cookies, old_url) + [forward]
end
if data.index("The e-mail address or password is incorrect")
raise AuthenticationError, "Username and password do not match"
elsif data != ""
raise AuthenticationError, "Required field must not be blank"
elsif cookies == ""
raise ConnectionError, PROTOCOL_ERROR
end
data, resp, cookies, forward = get("http://mail.live.com/mail", cookies)
until forward.nil?
data, resp, cookies, forward, old_url = get(forward, cookies, old_url) + [forward]
end
@domain = URI.parse(old_url).host
@cookies = cookies
rescue AuthenticationError => m
if @attempt == 1
retry
else
raise m
end
end
def contacts(options = {})
if connected?
@contacts = []
build_contacts = []
go = true
index = 0
while(go) do
go = false
data, resp, cookies, forward = get( get_contact_list_url(index), @cookies )
email_match_text_beginning = Regexp.escape("http://m.mail.live.com/?rru=compose&to=")
email_match_text_end = Regexp.escape("&ru=")
raw_html = resp.body.split("
").grep(/(?:e|dn)lk[0-9]+/)
raw_html.inject(-1) do |memo, row|
c_info = row.match(/(e|dn)lk([0-9])+/)
# Same contact, or different?
build_contacts << [] if memo != c_info[2]
# Grab info
case c_info[1]
when "e" # Email
build_contacts.last[1] = row.match(/#{email_match_text_beginning}(.*)#{email_match_text_end}/)[1]
when "dn" # Name
build_contacts.last[0] = row.match(/<a[^>]*>(.+)<\/a>/)[1]
end
# Set memo to contact id
c_info[2]
end
go = resp.body.include?("ContactList_next")
index += 1
end
build_contacts.each do |contact|
unless contact[1].nil?
# Only return contacts with email addresses
contact[1] = CGI::unescape CGI::unescape(contact[1])
@contacts << contact
end
end
return @contacts
end
end
def get_contact_list_url(index)
"http://mpeople.live.com/default.aspx?pg=#{index}"
end
private
TYPES[:hotmail] = Hotmail
end
end
================================================
FILE: lib/contacts/json_picker.rb
================================================
if !Object.const_defined?('ActiveSupport')
require 'json'
end
class Contacts
def self.parse_json( string )
if Object.const_defined?('ActiveSupport') and
ActiveSupport.const_defined?('JSON')
ActiveSupport::JSON.decode( string )
elsif Object.const_defined?('JSON')
JSON.parse( string )
else
raise 'Contacts requires JSON or Rails (with ActiveSupport::JSON)'
end
end
end
================================================
FILE: lib/contacts/mailru.rb
================================================
require 'csv'
class Contacts
class Mailru < Base
LOGIN_URL = "https://auth.mail.ru/cgi-bin/auth"
ADDRESS_BOOK_URL = "http://win.mail.ru/cgi-bin/abexport/addressbook.csv"
attr_accessor :cookies
def real_connect
username = login
postdata = "Login=%s&Domain=%s&Password=%s" % [
CGI.escape(username),
CGI.escape(domain_param(username)),
CGI.escape(password)
]
data, resp, self.cookies, forward = post(LOGIN_URL, postdata, "")
if data.index("fail=1")
raise AuthenticationError, "Username and password do not match"
elsif cookies == "" or data == ""
raise ConnectionError, PROTOCOL_ERROR
end
data, resp, cookies, forward = get(login_token_link(data), login_cookies.join(';'))
end
def contacts
postdata = "confirm=1&abtype=6"
data, resp, cookies, forward = post(ADDRESS_BOOK_URL, postdata, login_cookies.join(';'))
@contacts = []
CSV.parse(data) do |row|
@contacts << [row[0], row[4]] unless header_row?(row)
end
@contacts
end
def skip_gzip?
true
end
private
def login_token_link(data)
data.match(/url=(.+)\">/)[1]
end
def login_cookies
self.cookies.split(';').collect{|c| c if (c.include?('t=') or c.include?('Mpop='))}.compact.collect{|c| c.strip}
end
def header_row?(row)
row[0] == 'AB-Name'
end
def domain_param(login)
login.include?('@') ?
login.match(/.+@(.+)/)[1] :
'mail.ru'
end
end
TYPES[:mailru] = Mailru
end
================================================
FILE: lib/contacts/plaxo.rb
================================================
require 'rexml/document'
class Contacts
class Plaxo < Base
URL = "http://www.plaxo.com/"
LOGIN_URL = "https://www.plaxo.com/signin"
ADDRESS_BOOK_URL = "http://www.plaxo.com/po3/?module=ab&operation=viewFull&mode=normal"
CONTACT_LIST_URL = "http://www.plaxo.com/axis/soap/contact?_action=getContacts&_format=xml"
PROTOCOL_ERROR = "Plaxo has changed its protocols, please upgrade this library first. If that does not work, dive into the code and submit a patch at http://github.com/cardmagic/contacts"
def real_connect
end # real_connect
def contacts
getdata = "&authInfo.authByEmail.email=%s" % CGI.escape(login)
getdata += "&authInfo.authByEmail.password=%s" % CGI.escape(password)
data, resp, cookies, forward = get(CONTACT_LIST_URL + getdata)
if resp.code_type != Net::HTTPOK
raise ConnectionError, PROTOCOL_ERROR
end
parse data
end # contacts
private
def parse(data, options={})
doc = REXML::Document.new(data)
code = doc.elements['//response/code'].text
if code == '401'
raise AuthenticationError, "Username and password do not match"
elsif code == '200'
@contacts = []
doc.elements.each('//contact') do |cont|
name = if cont.elements['fullName']
cont.elements['fullName'].text
elsif cont.elements['displayName']
cont.elements['displayName'].text
end
email = if cont.elements['email1']
cont.elements['email1'].text
end
if name || email
@contacts << [name, email]
end
end
@contacts
else
raise ConnectionError, PROTOCOL_ERROR
end
end # parse
end # Plaxo
TYPES[:plaxo] = Plaxo
end # Contacts
# sample contacts responses
=begin
Bad email
=========
<?xml version="1.0" encoding="utf-8" ?>
<ns1:GetContactsResponse xmlns:ns1="Plaxo">
<response>
<code>401</code>
<subCode>1</subCode>
<message>User not found.</message>
</response>
</ns1:GetContactsResponse>
Bad password
============
<?xml version="1.0" encoding="utf-8" ?>
<ns1:GetContactsResponse xmlns:ns1="Plaxo">
<response>
<code>401</code>
<subCode>4</subCode>
<message>Bad password or security token.</message>
</response>
</ns1:GetContactsResponse>
Success
=======
<?xml version="1.0" encoding="utf-8" ?>
<ns1:GetContactsResponse xmlns:ns1="Plaxo">
<response>
<code>200</code>
<message>OK</message>
<userId>77311236242</userId>
</response>
<contacts>
<contact>
<itemId>61312569</itemId>
<displayName>Joe Blow1</displayName>
<fullName>Joe Blow1</fullName>
<firstName>Joe</firstName>
<lastName>Blow1</lastName>
<homeEmail1>joeblow1@mailinator.com</homeEmail1>
<email1>joeblow1@mailinator.com</email1>
<folderId>5291351</folderId>
</contact>
<contact>
<itemId>61313159</itemId>
<displayName>Joe Blow2</displayName>
<fullName>Joe Blow2</fullName>
<firstName>Joe</firstName>
<lastName>Blow2</lastName>
<homeEmail1>joeblow2@mailinator.com</homeEmail1>
<email1>joeblow2@mailinator.com</email1>
<folderId>5291351</folderId>
</contact>
</contacts>
<totalCount>2</totalCount>
<editCounter>3</editCounter>
</ns1:GetContactsResponse>
=end
================================================
FILE: lib/contacts/yahoo.rb
================================================
class Contacts
class Yahoo < Base
URL = "http://mail.yahoo.com/"
LOGIN_URL = "https://login.yahoo.com/config/login"
ADDRESS_BOOK_URL = "http://address.mail.yahoo.com/?.rand=430244936"
CONTACT_LIST_URL = "http://address.mail.yahoo.com/?_src=&_crumb=crumb&sortfield=3&bucket=1&scroll=1&VPC=social_list&.r=time"
PROTOCOL_ERROR = "Yahoo has changed its protocols, please upgrade this library first. If that does not work, dive into the code and submit a patch at http://github.com/cardmagic/contacts"
def real_connect
postdata = ".tries=2&.src=ym&.md5=&.hash=&.js=&.last=&promo=&.intl=us&.bypass="
postdata += "&.partner=&.u=4eo6isd23l8r3&.v=0&.challenge=gsMsEcoZP7km3N3NeI4mX"
postdata += "kGB7zMV&.yplus=&.emailCode=&pkg=&stepid=&.ev=&hasMsgr=1&.chkP=Y&."
postdata += "done=#{CGI.escape(URL)}&login=#{CGI.escape(login)}&passwd=#{CGI.escape(password)}"
data, resp, cookies, forward = post(LOGIN_URL, postdata)
if data.index("Invalid ID or password") || data.index("This ID is not yet taken")
raise AuthenticationError, "Username and password do not match"
elsif data.index("Sign in") && data.index("to Yahoo!")
raise AuthenticationError, "Required field must not be blank"
elsif !data.match(/uncompressed\/chunked/)
raise ConnectionError, PROTOCOL_ERROR
elsif cookies == ""
raise ConnectionError, PROTOCOL_ERROR
end
data, resp, cookies, forward = get(forward, cookies, LOGIN_URL)
if resp.code_type != Net::HTTPOK
raise ConnectionError, PROTOCOL_ERROR
end
@cookies = cookies
end
def contacts
return @contacts if @contacts
@contacts = []
if connected?
# first, get the addressbook site with the new crumb parameter
url = URI.parse(address_book_url)
http = open_http(url)
resp, data = http.get("#{url.path}?#{url.query}",
"Cookie" => @cookies
)
if resp.code_type != Net::HTTPOK
raise ConnectionError, self.class.const_get(:PROTOCOL_ERROR)
end
crumb = data.to_s[/dotCrumb: '(.*?)'/][13...-1]
# now proceed with the new ".crumb" parameter to get the csv data
url = URI.parse(contact_list_url.sub("_crumb=crumb","_crumb=#{crumb}").sub("time", Time.now.to_f.to_s.sub(".","")[0...-2]))
http = open_http(url)
resp, more_data = http.get("#{url.path}?#{url.query}",
"Cookie" => @cookies,
"X-Requested-With" => "XMLHttpRequest",
"Referer" => address_book_url
)
if resp.code_type != Net::HTTPOK
raise ConnectionError, self.class.const_get(:PROTOCOL_ERROR)
end
if more_data =~ /"TotalABContacts":(\d+)/
total = $1.to_i
((total / 50.0).ceil).times do |i|
# now proceed with the new ".crumb" parameter to get the csv data
url = URI.parse(contact_list_url.sub("bucket=1","bucket=#{i}").sub("_crumb=crumb","_crumb=#{crumb}").sub("time", Time.now.to_f.to_s.sub(".","")[0...-2]))
http = open_http(url)
resp, more_data = http.get("#{url.path}?#{url.query}",
"Cookie" => @cookies,
"X-Requested-With" => "XMLHttpRequest",
"Referer" => address_book_url
)
if resp.code_type != Net::HTTPOK
raise ConnectionError, self.class.const_get(:PROTOCOL_ERROR)
end
parse more_data
end
end
@contacts
end
end
private
def parse(data, options={})
@contacts ||= []
@contacts += Contacts.parse_json(data)["response"]["ResultSet"]["Contacts"].to_a.select{|contact|!contact["email"].to_s.empty?}.map do |contact|
name = contact["contactName"].split(",")
[[name.pop, name.join(",")].join(" ").strip, contact["email"]]
end if data =~ /^\{"response":/
@contacts
end
end
TYPES[:yahoo] = Yahoo
end
================================================
FILE: lib/contacts.rb
================================================
$:.unshift(File.dirname(__FILE__)+"/contacts/")
require 'rubygems'
require 'bundler/setup'
require 'base'
require 'json_picker'
require 'gmail'
require 'hotmail'
require 'yahoo'
require 'plaxo'
require 'aol'
require 'mailru'
================================================
FILE: test/example_accounts.yml
================================================
gmail:
username: <changeme>
password: <changeme>
contacts:
-
name: "FirstName1 LastName1"
email_address: "firstname1@example.com"
-
name: "FirstName2 LastName2"
email_address: "firstname2@example.com"
yahoo:
username: <changeme>
password: <changeme>
contacts:
-
name: "FirstName1 LastName1"
email_address: "firstname1@example.com"
-
name: "FirstName2 LastName2"
email_address: "firstname2@example.com"
hotmail:
username: <changeme>
password: <changeme>
contacts:
-
name: "FirstName1 LastName1"
email_address: "firstname1@example.com"
-
name: "FirstName2 LastName2"
email_address: "firstname2@example.com"
aol:
username: <changeme>
password: <changeme>
contacts:
-
name: "FirstName1 LastName1"
email_address: "firstname1@example.com"
-
name: "FirstName2 LastName2"
email_address: "firstname2@example.com"
mailru:
username: <changeme>
password: <changeme>
contacts:
-
name: "FirstName1 LastName1"
email_address: "firstname1@example.com"
-
name: "FirstName2 LastName2"
email_address: "firstname2@example.com"
================================================
FILE: test/test_helper.rb
================================================
dir = File.dirname(__FILE__)
$LOAD_PATH.unshift(dir + "/../lib/")
require 'test/unit'
require 'contacts'
require 'yaml'
class ContactImporterTestCase < Test::Unit::TestCase
# Add more helper methods to be used by all tests here...
def default_test
assert true
end
end
class TestAccounts
def self.[](type)
load[type]
end
def self.load(file = File.dirname(__FILE__) + "/accounts.yml")
raise "/test/accounts.yml file not found, please create, see /test/example_accounts.yml for information" unless File.exist?(file)
accounts = {}
YAML::load(File.open(file)).each do |type, contents|
contacts = contents["contacts"].collect {|contact| [contact["name"], contact["email_address"]]}
accounts[type.to_sym] = Account.new(type.to_sym, contents["username"], contents["password"], contacts)
end
accounts
end
Account = Struct.new :type, :username, :password, :contacts
end
================================================
FILE: test/test_suite.rb
================================================
dir = File.dirname(__FILE__)
Dir["#{dir}/**/*_test.rb"].each do |file|
require file
end
================================================
FILE: test/unit/aol_contact_importer_test.rb
================================================
dir = File.dirname(__FILE__)
require "#{dir}/../test_helper"
require 'contacts'
class AolContactImporterTest < ContactImporterTestCase
def setup
super
@account = TestAccounts[:aol]
end
def test_successful_login
Contacts.new(:aol, @account.username, @account.password)
end
def test_importer_fails_with_invalid_password
assert_raise(Contacts::AuthenticationError) do
Contacts.new(:aol, @account.username, "wrong_password")
end
end
def test_importer_fails_with_blank_password
assert_raise(Contacts::AuthenticationError) do
Contacts.new(:aol, @account.username, "")
end
end
def test_importer_fails_with_blank_username
assert_raise(Contacts::AuthenticationError) do
Contacts.new(:aol, "", @account.password)
end
end
def test_fetch_contacts
contacts = Contacts.new(:aol, @account.username, @account.password).contacts
@account.contacts.each do |contact|
assert contacts.include?(contact), "Could not find: #{contact.inspect} in #{contacts.inspect}"
end
end
end
================================================
FILE: test/unit/gmail_contact_importer_test.rb
================================================
dir = File.dirname(__FILE__)
require "#{dir}/../test_helper"
require 'contacts'
class GmailContactImporterTest < ContactImporterTestCase
def setup
super
@account = TestAccounts[:gmail]
end
def test_successful_login
Contacts.new(:gmail, @account.username, @account.password)
end
def test_importer_fails_with_invalid_password
assert_raise(Contacts::AuthenticationError) do
Contacts.new(:gmail, @account.username, "wrong_password")
end
end
def test_importer_fails_with_blank_password
assert_raise(Contacts::AuthenticationError) do
Contacts.new(:gmail, @account.username, "")
end
end
def test_importer_fails_with_blank_username
assert_raise(Contacts::AuthenticationError) do
Contacts.new(:gmail, "", @account.password)
end
end
def test_fetch_contacts
contacts = Contacts.new(:gmail, @account.username, @account.password).contacts
@account.contacts.each do |contact|
assert contacts.include?(contact), "Could not find: #{contact.inspect} in #{contacts.inspect}"
end
end
end
================================================
FILE: test/unit/hotmail_contact_importer_test.rb
================================================
dir = File.dirname(__FILE__)
require "#{dir}/../test_helper"
require 'contacts'
class HotmailContactImporterTest < ContactImporterTestCase
def setup
super
@account = TestAccounts[:hotmail]
end
def test_successful_login
Contacts.new(:hotmail, @account.username, @account.password)
end
def test_importer_fails_with_invalid_password
assert_raise(Contacts::AuthenticationError) do
Contacts.new(:hotmail, @account.username,"wrong_password")
end
end
def test_fetch_contacts
contacts = Contacts.new(:hotmail, @account.username, @account.password).contacts
@account.contacts.each do |contact|
assert contacts.include?(contact), "Could not find: #{contact.inspect} in #{contacts.inspect}"
end
end
def test_importer_fails_with_invalid_msn_password
assert_raise(Contacts::AuthenticationError) do
Contacts.new(:hotmail, "test@msn.com","wrong_password")
end
end
# Since the hotmail scraper doesn't read names, test email
def test_fetch_email
contacts = Contacts.new(:hotmail, @account.username, @account.password).contacts
@account.contacts.each do |contact|
assert contacts.any?{|book_contact| book_contact.last == contact.last }, "Could not find: #{contact.inspect} in #{contacts.inspect}"
end
end
end
================================================
FILE: test/unit/mailru_contact_importer_test.rb
================================================
dir = File.dirname(__FILE__)
require "#{dir}/../test_helper"
require 'contacts'
class MailruContactImporterTest < ContactImporterTestCase
def setup
super
@account = TestAccounts[:mailru]
end
def test_successful_login
Contacts.new(:mailru, @account.username, @account.password)
end
def test_importer_fails_with_invalid_password
assert_raise(Contacts::AuthenticationError) do
Contacts.new(:mailru, @account.username, "wrong_password")
end
end
def test_importer_fails_with_blank_password
assert_raise(Contacts::AuthenticationError) do
Contacts.new(:mailru, @account.username, "")
end
end
def test_importer_fails_with_blank_username
assert_raise(Contacts::AuthenticationError) do
Contacts.new(:mailru, "", @account.password)
end
end
def test_fetch_contacts
contacts = Contacts.new(:mailru, @account.username, @account.password).contacts
@account.contacts.each do |contact|
assert contacts.include?(contact), "Could not find: #{contact.inspect} in #{contacts.inspect}"
end
end
end
================================================
FILE: test/unit/test_accounts_test.rb
================================================
dir = File.dirname(__FILE__)
require "#{dir}/../test_helper"
class TestAccountsTest < ContactImporterTestCase
def test_test_accounts_loads_data_from_example_accounts_file
account = TestAccounts.load(File.dirname(__FILE__) + "/../example_accounts.yml")[:gmail]
assert_equal :gmail, account.type
assert_equal "<changeme>", account.username
assert_equal "<changeme>", account.password
assert_equal [["FirstName1 LastName1", "firstname1@example.com"], ["FirstName2 LastName2", "firstname2@example.com"]], account.contacts
end
def test_test_accounts_blows_up_if_file_doesnt_exist
assert_raise(RuntimeError) do
TestAccounts.load("file_that_does_not_exist.yml")
end
end
def test_we_can_load_from_account_file
assert_not_nil TestAccounts[:gmail].username
end
end
================================================
FILE: test/unit/yahoo_csv_contact_importer_test.rb
================================================
dir = File.dirname(__FILE__)
require "#{dir}/../test_helper"
require 'contacts'
class YahooContactImporterTest < ContactImporterTestCase
def setup
super
@account = TestAccounts[:yahoo]
end
def test_a_successful_login
Contacts.new(:yahoo, @account.username, @account.password)
end
def test_importer_fails_with_invalid_password
assert_raise(Contacts::AuthenticationError) do
Contacts.new(:yahoo, @account.username, "wrong_password")
end
# run the "successful" login test to ensure we reset yahoo's failed login lockout counter
# See http://www.pivotaltracker.com/story/show/138210
# yahoo needs some time to unset the failed login state, apparently...
# ...1 sec and 5 secs still failed sporadically
sleep 10
assert_nothing_raised do
Contacts.new(:yahoo, @account.username, @account.password)
end
end
def test_a_fetch_contacts
contacts = Contacts.new(:yahoo, @account.username, @account.password).contacts
@account.contacts.each do |contact|
assert contacts.include?(contact), "Could not find: #{contact.inspect} in #{contacts.inspect}"
end
end
end
gitextract_qyvd4b8y/
├── .gitignore
├── .travis.yml
├── Gemfile
├── LICENSE
├── README.md
├── Rakefile
├── contacts.gemspec
├── cruise_config.rb
├── examples/
│ └── grab_contacts.rb
├── geminstaller.yml
├── lib/
│ ├── contacts/
│ │ ├── aol.rb
│ │ ├── base.rb
│ │ ├── gmail.rb
│ │ ├── hotmail.rb
│ │ ├── json_picker.rb
│ │ ├── mailru.rb
│ │ ├── plaxo.rb
│ │ └── yahoo.rb
│ └── contacts.rb
└── test/
├── example_accounts.yml
├── test_helper.rb
├── test_suite.rb
└── unit/
├── aol_contact_importer_test.rb
├── gmail_contact_importer_test.rb
├── hotmail_contact_importer_test.rb
├── mailru_contact_importer_test.rb
├── test_accounts_test.rb
└── yahoo_csv_contact_importer_test.rb
SYMBOL INDEX (103 symbols across 15 files)
FILE: lib/contacts/aol.rb
class Contacts (line 1) | class Contacts
class Aol (line 4) | class Aol < Base
method real_connect (line 15) | def real_connect
method contacts (line 98) | def contacts
method parse (line 137) | def parse(data, options={})
method h_to_query_string (line 145) | def h_to_query_string(hash)
FILE: lib/contacts/base.rb
class Contacts (line 10) | class Contacts
class Base (line 14) | class Base
method initialize (line 15) | def initialize(login, password, options={})
method connect (line 24) | def connect
method connected? (line 29) | def connected?
method contacts (line 33) | def contacts(options = {})
method login (line 50) | def login
method password (line 65) | def password
method skip_gzip? (line 69) | def skip_gzip?
method domain (line 75) | def domain
method contact_list_url (line 79) | def contact_list_url
method address_book_url (line 83) | def address_book_url
method open_http (line 87) | def open_http(url)
method cookie_hash_from_string (line 102) | def cookie_hash_from_string(cookie_string)
method parse_cookies (line 106) | def parse_cookies(data, existing="")
method remove_cookie (line 132) | def remove_cookie(cookie, cookies)
method post (line 136) | def post(url, postdata, cookies="", referer="")
method get (line 157) | def get(url, cookies="", referer="")
method uncompress (line 181) | def uncompress(resp, data)
class ContactsError (line 199) | class ContactsError < StandardError
class AuthenticationError (line 202) | class AuthenticationError < ContactsError
class ConnectionError (line 205) | class ConnectionError < ContactsError
class TypeNotFound (line 208) | class TypeNotFound < ContactsError
method new (line 211) | def self.new(type, login, password, options={})
method guess (line 219) | def self.guess(login, password, options={})
FILE: lib/contacts/gmail.rb
class Contacts (line 3) | class Contacts
class Gmail (line 4) | class Gmail < Base
method contacts (line 9) | def contacts
method real_connect (line 13) | def real_connect
FILE: lib/contacts/hotmail.rb
class Contacts (line 1) | class Contacts
class Hotmail (line 2) | class Hotmail < Base
method real_connect (line 12) | def real_connect
method contacts (line 59) | def contacts(options = {})
method get_contact_list_url (line 110) | def get_contact_list_url(index)
FILE: lib/contacts/json_picker.rb
class Contacts (line 5) | class Contacts
method parse_json (line 6) | def self.parse_json( string )
FILE: lib/contacts/mailru.rb
class Contacts (line 3) | class Contacts
class Mailru (line 4) | class Mailru < Base
method real_connect (line 10) | def real_connect
method contacts (line 30) | def contacts
method skip_gzip? (line 42) | def skip_gzip?
method login_token_link (line 47) | def login_token_link(data)
method login_cookies (line 51) | def login_cookies
method header_row? (line 55) | def header_row?(row)
method domain_param (line 59) | def domain_param(login)
FILE: lib/contacts/plaxo.rb
class Contacts (line 3) | class Contacts
class Plaxo (line 4) | class Plaxo < Base
method real_connect (line 11) | def real_connect
method contacts (line 15) | def contacts
method parse (line 28) | def parse(data, options={})
FILE: lib/contacts/yahoo.rb
class Contacts (line 1) | class Contacts
class Yahoo (line 2) | class Yahoo < Base
method real_connect (line 9) | def real_connect
method contacts (line 36) | def contacts
method parse (line 93) | def parse(data, options={})
FILE: test/test_helper.rb
class ContactImporterTestCase (line 7) | class ContactImporterTestCase < Test::Unit::TestCase
method default_test (line 9) | def default_test
class TestAccounts (line 14) | class TestAccounts
method [] (line 15) | def self.[](type)
method load (line 19) | def self.load(file = File.dirname(__FILE__) + "/accounts.yml")
FILE: test/unit/aol_contact_importer_test.rb
class AolContactImporterTest (line 5) | class AolContactImporterTest < ContactImporterTestCase
method setup (line 6) | def setup
method test_successful_login (line 11) | def test_successful_login
method test_importer_fails_with_invalid_password (line 15) | def test_importer_fails_with_invalid_password
method test_importer_fails_with_blank_password (line 21) | def test_importer_fails_with_blank_password
method test_importer_fails_with_blank_username (line 27) | def test_importer_fails_with_blank_username
method test_fetch_contacts (line 33) | def test_fetch_contacts
FILE: test/unit/gmail_contact_importer_test.rb
class GmailContactImporterTest (line 5) | class GmailContactImporterTest < ContactImporterTestCase
method setup (line 6) | def setup
method test_successful_login (line 11) | def test_successful_login
method test_importer_fails_with_invalid_password (line 15) | def test_importer_fails_with_invalid_password
method test_importer_fails_with_blank_password (line 21) | def test_importer_fails_with_blank_password
method test_importer_fails_with_blank_username (line 27) | def test_importer_fails_with_blank_username
method test_fetch_contacts (line 33) | def test_fetch_contacts
FILE: test/unit/hotmail_contact_importer_test.rb
class HotmailContactImporterTest (line 5) | class HotmailContactImporterTest < ContactImporterTestCase
method setup (line 6) | def setup
method test_successful_login (line 11) | def test_successful_login
method test_importer_fails_with_invalid_password (line 15) | def test_importer_fails_with_invalid_password
method test_fetch_contacts (line 21) | def test_fetch_contacts
method test_importer_fails_with_invalid_msn_password (line 28) | def test_importer_fails_with_invalid_msn_password
method test_fetch_email (line 35) | def test_fetch_email
FILE: test/unit/mailru_contact_importer_test.rb
class MailruContactImporterTest (line 5) | class MailruContactImporterTest < ContactImporterTestCase
method setup (line 6) | def setup
method test_successful_login (line 11) | def test_successful_login
method test_importer_fails_with_invalid_password (line 15) | def test_importer_fails_with_invalid_password
method test_importer_fails_with_blank_password (line 21) | def test_importer_fails_with_blank_password
method test_importer_fails_with_blank_username (line 27) | def test_importer_fails_with_blank_username
method test_fetch_contacts (line 33) | def test_fetch_contacts
FILE: test/unit/test_accounts_test.rb
class TestAccountsTest (line 4) | class TestAccountsTest < ContactImporterTestCase
method test_test_accounts_loads_data_from_example_accounts_file (line 5) | def test_test_accounts_loads_data_from_example_accounts_file
method test_test_accounts_blows_up_if_file_doesnt_exist (line 14) | def test_test_accounts_blows_up_if_file_doesnt_exist
method test_we_can_load_from_account_file (line 20) | def test_we_can_load_from_account_file
FILE: test/unit/yahoo_csv_contact_importer_test.rb
class YahooContactImporterTest (line 5) | class YahooContactImporterTest < ContactImporterTestCase
method setup (line 6) | def setup
method test_a_successful_login (line 11) | def test_a_successful_login
method test_importer_fails_with_invalid_password (line 15) | def test_importer_fails_with_invalid_password
method test_a_fetch_contacts (line 29) | def test_a_fetch_contacts
Condensed preview — 28 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (48K chars).
[
{
"path": ".gitignore",
"chars": 45,
"preview": ".DS_Store\npkg/*\ntest/accounts.yml\nnbproject/\n"
},
{
"path": ".travis.yml",
"chars": 152,
"preview": "language: ruby\nrvm:\n - 1.9.3\n - 1.9.2\n - jruby-18mode\n - jruby-19mode\n - rbx-18mode\n - rbx-19mode\n - ruby-head\n "
},
{
"path": "Gemfile",
"chars": 134,
"preview": "source 'https://rubygems.org'\ngem 'json', \">= 1.1.1\"\ngem 'gdata_19', '1.1.5'\ngem 'rspec', :require => 'spec'\ngem 'rake'\n"
},
{
"path": "LICENSE",
"chars": 1474,
"preview": "Copyright (c) 2006, Lucas Carlson, MOG\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or "
},
{
"path": "README.md",
"chars": 2856,
"preview": "Welcome to Contacts\n===================\n\nContacts is a universal interface to grab contact list information from various"
},
{
"path": "Rakefile",
"chars": 2356,
"preview": "require 'rubygems'\nrequire 'bundler/setup'\nrequire 'rake'\nrequire 'rake/testtask'\nrequire 'rake/rdoctask'\nrequire 'rake/"
},
{
"path": "contacts.gemspec",
"chars": 878,
"preview": "Gem::Specification.new do |s|\n s.name = \"contacts\"\n s.version = \"1.2.4\"\n s.date = \"2010-07-06\"\n s.summary = \"A unive"
},
{
"path": "cruise_config.rb",
"chars": 974,
"preview": "# Project-specific configuration for CruiseControl.rb\n\nProject.configure do |project|\n\n # Send email notifications abou"
},
{
"path": "examples/grab_contacts.rb",
"chars": 271,
"preview": "require File.dirname(__FILE__)+\"/../lib/contacts\"\n\nlogin = ARGV[0]\npassword = ARGV[1]\n\nContacts::Gmail.new(login, passwo"
},
{
"path": "geminstaller.yml",
"chars": 132,
"preview": "---\ndefaults:\n install_options: --no-ri --no-rdoc\ngems:\n - name: json\n version: >= 1.1.1\n - name: gdata\n versio"
},
{
"path": "lib/contacts/aol.rb",
"chars": 5711,
"preview": "class Contacts\n require 'hpricot'\n require 'csv'\n class Aol < Base\n URL = \"http://www.aol.com/\"\n "
},
{
"path": "lib/contacts/base.rb",
"chars": 6312,
"preview": "require \"cgi\"\nrequire \"net/http\"\nrequire \"net/https\"\nrequire \"uri\"\nrequire \"zlib\"\nrequire \"stringio\"\nrequire \"thread\"\nre"
},
{
"path": "lib/contacts/gmail.rb",
"chars": 984,
"preview": "require 'gdata'\n\nclass Contacts\n class Gmail < Base\n \n CONTACTS_SCOPE = 'http://www.google.com/m8/feeds/'\n CON"
},
{
"path": "lib/contacts/hotmail.rb",
"chars": 3986,
"preview": "class Contacts\n class Hotmail < Base\n URL = \"https://login.live.com/login.srf?id=2\"\n OLD_CONTACT_"
},
{
"path": "lib/contacts/json_picker.rb",
"chars": 415,
"preview": "if !Object.const_defined?('ActiveSupport')\n require 'json'\nend\n\nclass Contacts\n def self.parse_json( string )\n if O"
},
{
"path": "lib/contacts/mailru.rb",
"chars": 1591,
"preview": "require 'csv'\n\nclass Contacts\n class Mailru < Base\n LOGIN_URL = \"https://auth.mail.ru/cgi-bin/auth\"\n ADDRESS_BOOK"
},
{
"path": "lib/contacts/plaxo.rb",
"chars": 3469,
"preview": "require 'rexml/document'\n\nclass Contacts\n class Plaxo < Base\n URL = \"http://www.plaxo.com/\"\n LOGI"
},
{
"path": "lib/contacts/yahoo.rb",
"chars": 4028,
"preview": "class Contacts\n class Yahoo < Base\n URL = \"http://mail.yahoo.com/\"\n LOGIN_URL = \"https:"
},
{
"path": "lib/contacts.rb",
"chars": 228,
"preview": "$:.unshift(File.dirname(__FILE__)+\"/contacts/\")\n\nrequire 'rubygems'\nrequire 'bundler/setup'\n\nrequire 'base'\nrequire 'jso"
},
{
"path": "test/example_accounts.yml",
"chars": 1196,
"preview": "gmail:\n username: <changeme>\n password: <changeme>\n contacts:\n -\n name: \"FirstName1 LastName1\"\n email_ad"
},
{
"path": "test/test_helper.rb",
"chars": 958,
"preview": "dir = File.dirname(__FILE__)\r\n$LOAD_PATH.unshift(dir + \"/../lib/\")\r\nrequire 'test/unit'\r\nrequire 'contacts'\r\nrequire 'ya"
},
{
"path": "test/test_suite.rb",
"chars": 92,
"preview": "dir = File.dirname(__FILE__)\r\nDir[\"#{dir}/**/*_test.rb\"].each do |file|\r\n require file\r\nend"
},
{
"path": "test/unit/aol_contact_importer_test.rb",
"chars": 1056,
"preview": "dir = File.dirname(__FILE__)\nrequire \"#{dir}/../test_helper\"\nrequire 'contacts'\n\nclass AolContactImporterTest < ContactI"
},
{
"path": "test/unit/gmail_contact_importer_test.rb",
"chars": 1070,
"preview": "dir = File.dirname(__FILE__)\nrequire \"#{dir}/../test_helper\"\nrequire 'contacts'\n\nclass GmailContactImporterTest < Contac"
},
{
"path": "test/unit/hotmail_contact_importer_test.rb",
"chars": 1344,
"preview": "dir = File.dirname(__FILE__)\r\nrequire \"#{dir}/../test_helper\"\r\nrequire 'contacts'\r\n\r\nclass HotmailContactImporterTest < "
},
{
"path": "test/unit/mailru_contact_importer_test.rb",
"chars": 1077,
"preview": "dir = File.dirname(__FILE__)\nrequire \"#{dir}/../test_helper\"\nrequire 'contacts'\n\nclass MailruContactImporterTest < Conta"
},
{
"path": "test/unit/test_accounts_test.rb",
"chars": 816,
"preview": "dir = File.dirname(__FILE__)\nrequire \"#{dir}/../test_helper\"\n\nclass TestAccountsTest < ContactImporterTestCase\n def tes"
},
{
"path": "test/unit/yahoo_csv_contact_importer_test.rb",
"chars": 1179,
"preview": "dir = File.dirname(__FILE__)\r\nrequire \"#{dir}/../test_helper\"\r\nrequire 'contacts'\r\n\r\nclass YahooContactImporterTest < Co"
}
]
About this extraction
This page contains the full source code of the cardmagic/contacts GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 28 files (43.7 KB), approximately 12.3k tokens, and a symbol index with 103 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.