Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Comparing changes

Choose two branches to see what's changed or to start a new pull request. If you need to, you can also compare across forks.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also compare across forks.
base fork: herimedia/contacts
base: 72dd554d34
...
head fork: herimedia/contacts
compare: 336215b1c8
Checking mergeability… Don't worry, you can still create the pull request.
  • 18 commits
  • 15 files changed
  • 0 commit comments
  • 2 contributors
View
1  contacts.gemspec
@@ -11,4 +11,5 @@ Gem::Specification.new do |s|
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.1')
+ s.add_dependency('mail', '>=2.2.0')
end
View
4 lib/contacts.rb
@@ -9,4 +9,8 @@
require 'yahoo'
require 'plaxo'
require 'aol'
+require 'yandex'
+require 'rambler'
require 'mailru'
+require 'livejournal'
+require 'vkontakte'
View
5 lib/contacts/aol.rb
@@ -138,7 +138,10 @@ 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?
+ email = person[4]
+ name = "#{person[0]} #{person[1]}".strip
+ name = email if name.empty? && !email.empty?
+ {:id => email, :name => name} unless email.empty?
end.compact
end
View
31 lib/contacts/base.rb
@@ -6,6 +6,15 @@
require "stringio"
require "thread"
require "erb"
+require "mail"
+
+module Mail
+ class SubjectField
+ def fold *args
+ @folded_line << encode(@unfolded_line)
+ end
+ end
+end
class Contacts
TYPES = {}
@@ -18,12 +27,15 @@ def initialize(login, password, options={})
@captcha_token = options[:captcha_token]
@captcha_response = options[:captcha_response]
@connections = {}
+ @skip_login = options[:skip_login]
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
+ unless @skip_login
+ 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
end
def connected?
@@ -70,6 +82,21 @@ def skip_gzip?
false
end
+ def send_message(email, subject, text, params = {}, smtp_settings = {})
+ mail = Mail.new
+ mail.delivery_method :smtp, smtp_settings
+ mail.to email
+ mail.subject subject
+ mail.body text
+ mail.from params[:from]
+
+ mail.content_type = 'text/html'
+ mail.charset = 'UTF-8'
+ mail.content_transfer_encoding = '8bit'
+
+ mail.deliver!
+ end
+
private
def domain
View
7 lib/contacts/gmail.rb
@@ -7,6 +7,7 @@ class Gmail < Base
CONTACTS_FEED = CONTACTS_SCOPE + 'contacts/default/full/?max-results=1000'
def contacts
+ @contacts.sort! { |a,b| a[:name] <=> b[:name] } if @contacts
return @contacts if @contacts
end
@@ -21,7 +22,11 @@ def real_connect
entry.elements.each('gd:email') do |e|
email = e.attribute('address').value if e.attribute('primary')
end
- [title, email] unless email.nil?
+ name = title.nil? ? '' : title
+ unless email.nil?
+ name = email if name.empty? && !email.empty?
+ {:id => email, :name => name} unless email.empty?
+ end
end
@contacts.compact!
rescue GData::Client::AuthorizationError => e
View
5 lib/contacts/hotmail.rb
@@ -106,7 +106,10 @@ def contacts(options = {})
unless contact[1].nil?
# Only return contacts with email addresses
contact[1] = CGI::unescape(contact[1])
- @contacts << contact
+ email = contact[1]
+ name = contact[1].strip
+ name = email if name.empty? && !email.empty?
+ @contacts << {:id => email, :name => name}
end
end
View
79 lib/contacts/livejournal.rb
@@ -0,0 +1,79 @@
+require 'hpricot'
+require 'mechanize'
+
+class Contacts
+ class Livejournal < Base
+ URL = "http://livejournal.com/"
+ LOGIN_URL = "http://www.livejournal.com/mobile/login.bml"
+ ADDRESS_BOOK_URL = "http://www.livejournal.com/friends/edit.bml"
+ PROTOCOL_ERROR = "Livejournal.com has changed its protocols, please upgrade this library first."
+
+ def real_connect
+ postdata = "user=#{CGI.escape(login.to_s)}&password=#{CGI.escape(password.to_s)}"
+
+ data, resp, cookies, forward = post(LOGIN_URL, postdata)
+ old_url = LOGIN_URL
+ until forward.nil?
+ data, resp, cookies, forward, old_url = get(forward, cookies, old_url) + [forward]
+ end
+
+ if data.index("Invalid username") || data.index("Bad password")
+ raise AuthenticationError, "Username and password do not match"
+ elsif cookies == ""
+ raise ConnectionError, PROTOCOL_ERROR
+ elsif resp.code_type != Net::HTTPOK
+ raise ConnectionError, PROTOCOL_ERROR
+ end
+
+ @cookies = cookies
+ end
+
+ def contacts
+ @contacts = []
+ if connected?
+ 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
+ doc = Hpricot(data)
+ doc.at("#editfriends").search("span.ljuser").each do |span|
+ user = span.inner_text
+ @contacts << {:id => user, :name => user}
+ end
+ @contacts
+ end
+
+ @contacts.sort! { |a,b| a[:name] <=> b[:name] } if @contacts
+ return @contacts if @contacts
+ end
+
+ def send_message(username, subject, text)
+ begin
+ agent = Mechanize.new
+
+ page = agent.get "http://livejournal.com"
+ login_form = page.forms[4]
+ login_form.user = login.split('@')[0].to_s
+ login_form.password = password.to_s
+
+ page = agent.submit(login_form)
+
+ page = agent.get "http://www.livejournal.com/inbox/compose.bml?user=#{username.to_s}"
+
+ msg_form = page.forms[1]
+ msg_form.msg_subject = subject.to_s
+ msg_form.msg_body = text.to_s
+
+ page = agent.submit(msg_form)
+ return true
+ rescue
+ raise ConnectionError, PROTOCOL_ERROR
+ end
+ end
+
+ end
+
+ TYPES[:livejournal] = Livejournal
+end
View
123 lib/contacts/mailru.rb
@@ -1,68 +1,91 @@
-require 'csv'
+require 'hpricot'
+require 'iconv'
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
-
+ URL = "http://mail.ru/"
+ LOGIN_URL = "http://win.mail.ru/cgi-bin/auth"
+ ADDRESS_BOOK_URL = "http://win.mail.ru/cgi-bin/addressbook?viewmode=l"
+ PROTOCOL_ERROR = "Mail.ru has changed its protocols, please upgrade this library first."
+
def real_connect
- username = login
+ postdata = "Domain=#{CGI.escape(login.split('@')[1].to_s)}&Login=#{CGI.escape(login.split('@')[0].to_s)}&Password=#{CGI.escape(password.to_s)}"
- 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")
+ data, resp, cookies, forward = post(LOGIN_URL, postdata)
+ old_url = LOGIN_URL
+ until forward.nil?
+ data, resp, cookies, forward, old_url = get(forward, cookies, old_url) + [forward]
+ end
+ data = Iconv.iconv("UTF-8", "WINDOWS-1251", data)[0]
+
+ if data.index("Неверное имя пользователя или пароль") || data.index("Недопустимое имя пользователя")
raise AuthenticationError, "Username and password do not match"
- elsif cookies == "" or data == ""
+ elsif cookies == ""
raise ConnectionError, PROTOCOL_ERROR
end
-
- data, resp, cookies, forward = get(login_token_link(data), login_cookies.join(';'))
+
+ if resp.code_type != Net::HTTPOK
+ raise ConnectionError, PROTOCOL_ERROR
+ end
+
+ @cookies = cookies
end
-
- def contacts
- postdata = "confirm=1&abtype=6"
- data, resp, cookies, forward = post(ADDRESS_BOOK_URL, postdata, login_cookies.join(';'))
-
+
+ def contacts
@contacts = []
- CSV.parse(data) do |row|
- @contacts << [row[0], row[4]] unless header_row?(row)
+ if connected?
+ page = 0
+ url = URI.parse(address_book_url)
+ begin
+ page += 1
+ http = open_http(url)
+ resp, data = http.get("#{url.path}?#{url.query}&page=#{page}",
+ "Cookie" => @cookies
+ )
+ if resp.code_type != Net::HTTPOK
+ raise ConnectionError, self.class.const_get(:PROTOCOL_ERROR)
+ end
+ data = Iconv.iconv("UTF-8", "WINDOWS-1251", data)[0]
+ doc = Hpricot(data)
+ tables = data.gsub(/\n/, "").gsub(/\r/, "").gsub(/<script(.*?)<\/script>/, "").scan(/<table.*?<\/table>/m)
+ table_id = 1
+ tables.each_index do |i|
+ table_id = i if tables[i].include?('adr_book')
+ end
+ Hpricot(tables[table_id]).search("tr").each do |tr|
+ unless tr["id"].nil?
+ name = tr.at("td.nik a").inner_text
+ email = tr.at("td.mail a").inner_text
+ name = email if name.strip.empty? && !email.empty?
+ @contacts << {:id => email, :name => name}
+ end
+ end
+ end while data.include?("<a href=\"?page=#{page+1}\">Далее<b>&nbsp;&#8250;</b></a>")
+ @contacts
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'
+
+ @contacts.sort! { |a,b| a[:name] <=> b[:name] } if @contacts
+ return @contacts if @contacts
end
+
+ private
+ 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
+ when 'deflate':
+ data = Zlib::Inflate.inflate(data)
+ resp.response['content-encoding'] = nil
+ end
- def domain_param(login)
- login.include?('@') ?
- login.match(/.+@(.+)/)[1] :
- 'mail.ru'
+ data
end
+
end
TYPES[:mailru] = Mailru
-end
+end
View
73 lib/contacts/rambler.rb
@@ -0,0 +1,73 @@
+require 'hpricot'
+
+class Contacts
+ class Rambler < Base
+ URL = "http://mail.rambler.ru/"
+ LOGIN_URL = "http://id.rambler.ru/script/auth.cgi"
+ ADDRESS_BOOK_URL = "http://mail.rambler.ru/mail/contacts.cgi"
+ PROTOCOL_ERROR = "Rambler has changed its protocols, please upgrade this library first."
+
+ def real_connect
+ postdata = "back=#{CGI.escape(ADDRESS_BOOK_URL)}&login=#{CGI.escape(login.split('@')[0].to_s)}&long_session=on&passw=#{CGI.escape(password.to_s)}&submit=Войти&url=7"
+
+ data, resp, cookies, forward = post(LOGIN_URL, postdata)
+ data = Iconv.iconv("UTF8", "CP1251", data)[0]
+
+ if data.include?("Пожалуйста, представьтесь, чтобы получить доступ к персональным службам Рамблера") || data.include?("Неправильное имя или пароль")
+ raise AuthenticationError, "Username and password do not match"
+ elsif cookies == ""
+ raise ConnectionError, PROTOCOL_ERROR
+ end
+
+ data, resp, cookies, forward = get(forward, cookies, LOGIN_URL)
+ 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?
+ 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
+ doc = Hpricot(data)
+ doc.at("#contacts-list").search("tr.vcard").each do |tr|
+ email = tr.at("a.email").inner_text
+ name = tr.at("td.fn").inner_text.blank? ? nil : tr.at("td.fn").inner_text
+ name = email if name.strip.empty? && !email.empty?
+ @contacts << {:id => email, :name => name}
+ end
+ @contacts
+ end
+ end
+
+ private
+ # 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
+ # when 'deflate':
+ # data = Zlib::Inflate.inflate(data)
+ # resp.response['content-encoding'] = nil
+ # end
+ #
+ # data
+ # end
+
+
+ end
+
+ TYPES[:rambler] = Rambler
+end
View
83 lib/contacts/vkontakte.rb
@@ -0,0 +1,83 @@
+require 'json'
+require 'mechanize'
+require 'iconv'
+
+class Contacts
+ class Vkontakte < Base
+ LOGIN_URL = 'http://vkontakte.ru'
+ ADDRESS_BOOK_URL = 'http://vkontakte.ru/friends.php'
+ PROTOCOL_ERROR = "Vkontakte.ru has changed its protocols, please upgrade this library first."
+
+ def real_connect
+ @agent = Mechanize.new
+ page = @agent.get(LOGIN_URL)
+ login_form = page.form('login')
+ login_form.email = login
+ login_form.pass = password
+
+ @agent.submit( @agent.submit(login_form, login_form.buttons.first).forms.first )
+ end
+
+ def contacts
+ @contacts = []
+
+ names = {}
+ my_friends = []
+ people = {}
+
+ my_friends_list = get_friends_list_from_page @agent.get(ADDRESS_BOOK_URL).body
+
+ if my_friends_list == {}
+ raise AuthenticationError, "Username and password do not match"
+ end
+
+ my_friends_list['friends'].each do |friend|
+ id = friend[0].to_s.strip
+ name = friend[1].strip
+ name = Iconv.iconv("UTF-8", "WINDOWS-1251", name)[0]
+ @contacts << {:id => id.to_s, :name => name}
+ end
+
+ @contacts.sort! { |a,b| a[:name] <=> b[:name] } if @contacts
+ return @contacts if @contacts
+ end
+
+ def send_message(id, subject, text)
+ begin
+ page = @agent.get "http://vkontakte.ru/mail.php?act=write&to=#{id.to_i}"
+ page.encoding = 'utf-8'
+
+ msg_form = page.form('postMessage')
+ msg_form.action = "http://vkontakte.ru/mail.php"
+ msg_form.title = subject
+ msg_form.message = text
+ msg_form.add_field!('ajax', '1')
+ msg_form.add_field!('misc', '')
+ msg_form.add_field!('toFriends', '')
+ msg_form.chas = "#{msg_form.chas[4..16].reverse}#{msg_form.chas[20..26].reverse}"
+
+ @agent.submit(msg_form, nil, 'X-Requested-With' => 'XMLHttpRequest')
+ return true
+ rescue
+ raise ConnectionError, PROTOCOL_ERROR
+ end
+ end
+
+ private
+
+ def get_friends_list_from_page page_body
+ friends_json_match = page_body.match(/^\s+var friendsData = (\{.+\});$/)
+ return {} if friends_json_match.nil?
+ friends_json = friends_json_match[1].gsub("'","\"").gsub(/(\d+):/,"\"$1\":").gsub(/[\x00-\x19]/," ")
+
+ begin
+ JSON.parse friends_json
+ rescue
+ raise ConnectionError, PROTOCOL_ERROR
+ end
+ end
+
+ end
+
+ TYPES[:vkontakte] = Vkontakte
+end
View
6 lib/contacts/yahoo.rb
@@ -83,7 +83,13 @@ def contacts
parse more_data
end
end
+
+ _tmp_contacts = []
+ @contacts.each do |e|
+ _tmp_contacts << {:name => e[0], :id => e[1]}
+ end
+ @contacts = _tmp_contacts
@contacts
end
end
View
63 lib/contacts/yandex.rb
@@ -0,0 +1,63 @@
+require 'csv'
+require 'iconv'
+
+class Contacts
+ class Yandex < Base
+ URL = "http://mail.yandex.ru/"
+ LOGIN_URL = "https://passport.yandex.ru/passport?mode=auth"
+ ADDRESS_BOOK_URL = "http://mail.yandex.ru/neo/ajax/action_abook_export"
+ PROTOCOL_ERROR = "Yandex has changed its protocols, please upgrade this library first."
+
+ def real_connect
+ postdata = "timestamp=&twoweeks=yes&login=#{CGI.escape(login)}&passwd=#{CGI.escape(password)}"
+
+ data, resp, cookies, forward = post(LOGIN_URL, postdata)
+ old_url = LOGIN_URL
+ until forward.nil?
+ data, resp, cookies, forward, old_url = get(forward, cookies, old_url) + [forward]
+ end
+
+ if data.index("Неправильная пара логин-пароль!")
+ raise AuthenticationError, "Username and password do not match"
+ elsif cookies == ""
+ raise ConnectionError, PROTOCOL_ERROR
+ elsif resp.code_type != Net::HTTPOK
+ raise ConnectionError, PROTOCOL_ERROR
+ end
+
+ @cookies = cookies
+ end
+
+ def contacts
+ @contacts = []
+ if connected?
+ data, resp, cookies, forward = post(address_book_url, "tp=4&rus=0", @cookies)
+ if resp.code_type != Net::HTTPOK
+ raise ConnectionError, self.class.const_get(:PROTOCOL_ERROR)
+ end
+
+ parse data
+ end
+
+ @contacts.sort! { |a,b| a[:name] <=> b[:name] } if @contacts
+ return @contacts if @contacts
+ end
+
+ private
+
+ def parse(data, options={})
+ data = CSV.parse(data)
+ col_names = data.shift
+ @contacts = data.map do |person|
+ email = person[4]
+ name = person[2].to_s.strip
+ name = email if name.empty? && !email.empty?
+ {:id => name, :name => email} unless email.empty?
+ end.compact
+ end
+
+
+ end
+
+ TYPES[:yandex] = Yandex
+end
View
39 test/unit/livejournal_contact_importer_test.rb
@@ -0,0 +1,39 @@
+dir = File.dirname(__FILE__)
+require "#{dir}/../test_helper"
+require 'contacts'
+
+class LivejournalContactImporterTest < ContactImporterTestCase
+ def setup
+ super
+ @account = TestAccounts[:livejournal]
+ end
+
+ def test_successful_login
+ Contacts.new(:livejournal, @account.username, @account.password)
+ end
+
+ def test_importer_fails_with_invalid_password
+ assert_raise(Contacts::AuthenticationError) do
+ Contacts.new(:livejournal, @account.username, "wrong_password")
+ end
+ end
+
+ def test_importer_fails_with_blank_password
+ assert_raise(Contacts::AuthenticationError) do
+ Contacts.new(:livejournal, @account.username, "")
+ end
+ end
+
+ def test_importer_fails_with_blank_username
+ assert_raise(Contacts::AuthenticationError) do
+ Contacts.new(:livejournal, "", @account.password)
+ end
+ end
+
+ def test_fetch_contacts
+ contacts = Contacts.new(:livejournal, @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
View
39 test/unit/vkontakte_contact_importer.rb
@@ -0,0 +1,39 @@
+dir = File.dirname(__FILE__)
+require "#{dir}/../test_helper"
+require 'contacts'
+
+class VkontakteContactImporterTest < ContactImporterTestCase
+ def setup
+ super
+ @account = TestAccounts[:vkontakte]
+ end
+
+ def test_successful_login
+ Contacts.new(:vkontakte, @account.username, @account.password).contacts
+ end
+
+ def test_importer_fails_with_invalid_password
+ assert_raise(Contacts::AuthenticationError) do
+ Contacts.new(:vkontakte, @account.username, "wrong_password").contacts
+ end
+ end
+
+ def test_importer_fails_with_blank_password
+ assert_raise(Contacts::AuthenticationError) do
+ Contacts.new(:vkontakte, @account.username, "").contacts
+ end
+ end
+
+ def test_importer_fails_with_blank_username
+ assert_raise(Contacts::AuthenticationError) do
+ Contacts.new(:vkontakte, "", @account.password).contacts
+ end
+ end
+
+ def test_fetch_contacts
+ contacts = Contacts.new(:vkontakte, @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
View
39 test/unit/yandex_contact_importer_test.rb
@@ -0,0 +1,39 @@
+dir = File.dirname(__FILE__)
+require "#{dir}/../test_helper"
+require 'contacts'
+
+class YandexContactImporterTest < ContactImporterTestCase
+ def setup
+ super
+ @account = TestAccounts[:yandex]
+ end
+
+ def test_successful_login
+ Contacts.new(:yandex, @account.username, @account.password)
+ end
+
+ def test_importer_fails_with_invalid_password
+ assert_raise(Contacts::AuthenticationError) do
+ Contacts.new(:yandex, @account.username, "wrong_password")
+ end
+ end
+
+ def test_importer_fails_with_blank_password
+ assert_raise(Contacts::AuthenticationError) do
+ Contacts.new(:yandex, @account.username, "")
+ end
+ end
+
+ def test_importer_fails_with_blank_username
+ assert_raise(Contacts::AuthenticationError) do
+ Contacts.new(:yandex, "", @account.password)
+ end
+ end
+
+ def test_fetch_contacts
+ contacts = Contacts.new(:yandex, @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

No commit comments for this range

Something went wrong with that request. Please try again.