#!/usr/bin/perl # # soocial2fastmail - Push contact data from Soocial to FastMail.FM # # This script copies (parts of) data in Soocial to the address book at # FastMail.FM. Following data are copied: # # name, email addresses and phone numbers (cell, home, work) # # The script uses SOAP (SOAP is not "simple"!) to access data stored at # FastMail.FM. The SOAP::MessaginEngine module can be found at: # # http://wiki.fastmail.fm/index.php?title=FastServicesPerl # # Otherwise, just save the script, install the necessary modules, change # usernames and passwords (read the comments) and just run it. # # The script uses Soocial the master source of data. On the first run, all # contacts will be copied to FastMail.FM. The copied contacts will have a note # that begins with "soocial:". This can be used to search for contacts that are # being kept of by this script. # # Contacts deleted from Soocial will be deleted from the address book. Updated # contacts will be... updated (what a surprise!). # # Changes made to the copied contacts in FastMail.FM will *not* be copied back # to Soocial (it's not so difficult to add a fix for this tough, but I don't # need it right now). # # This script does the trick for me. If it works for you - fine. # When you find a bug, please send me a patch. I'm saying "when" # and not "if" - all code has bugs. # # Thanks. # # P.S. Use at own risk, don't blame me (or Soocial or FastMail) if something # goes wrong or all your addresses are deleted. # # Copyright (c) by Kirill Miazine # # This software is distributed under an ISC-style license, please see # for details. # use strict; use warnings; use lib qw(/local/lib/perl); use LWP::UserAgent; use XML::Simple; use URI; use Date::Parse; use Date::Format; use Data::Dumper; use Encode qw(decode_utf8); use SOAP::MessagingEngine; import SOAP::Data qw(name value); ### CHANGE BELOW THIS LINE!!!!!!!!! my $soocial_user = ''; my $soocial_pass = ''; my $fastmail_user = ''; my $fastmail_pass = ''; ### DON'T CHANGE BELOW THIS LINE!!! my $ua = LWP::UserAgent->new(); $ua->credentials('www.soocial.com:443', 'Web Password', $soocial_user, $soocial_pass); my $soocial_url = 'https://www.soocial.com/contacts.xml'; my $page_num = 0; my $page_size = 100; my @contacts; while (1) { my $res = $ua->get("$soocial_url?page_size=$page_size&page_num=$page_num"); die $res->status_line if !$res->is_success; exit if $res->content() !~ /xml/; last if $res->content() !~ /contacts/; my $xml = XMLin(decode_utf8($res->content()), ForceArray => 1); for my $i (@{$xml->{'contact'}}) { my $id = int($i->{'id'}->[0]->{'content'}); my $created = int(str2time($i->{'created-at'}->[0]->{'content'})); my $updated = int(str2time($i->{'updated-at'}->[0]->{'content'})); push @contacts, {id => $id, created => $created, updated => $updated}; my $deleted = $i->{'deleted'}->[0]->{'content'} eq 'false' ? 0 : 1; @{$contacts[-1]}{qw(name deleted)} = ('deleted', 1), next if $deleted; $contacts[-1]->{'name'} = $i->{'formatted-name'}->[0]; $contacts[-1]->{'first'} = $i->{'given-name'}->[0]; $contacts[-1]->{'last'} = $i->{'family-name'}->[0]; my @email; if (exists $i->{'emails'}->[0]->{'email'}) { for my $z (@{$i->{'emails'}->[0]->{'email'}}) { my $address = $z->{'address'}->[0]; my $type = $z->{'location'}->[0]; push @email, [$address, lc $type]; } } $contacts[-1]->{'email'} = \@email; my @phone; if (exists $i->{'telephones'}->[0]->{'telephone'}) { for my $z (@{$i->{'telephones'}->[0]->{'telephone'}}) { my $number = $z->{'number'}->[0]; $number =~ tr/0-9+-//cd; my $type = $z->{'location'}->[0]; push @phone, [$number, lc $type]; } } $contacts[-1]->{'phone'} = \@phone; } $page_num++; } @contacts = sort { $a->{'name'} cmp $b->{'name'} } @contacts; my $sess = SOAP::MessagingEngine->new('https://www.fastmail.fm/SOAPB/', $fastmail_user, $fastmail_pass) or exit; my ($table, $fields, $fvals, $crit, $rows); $table = name(Table => 'Addresses'); $fields = name( FieldList => value([name(Field => 'AddressId'), name(Field => 'LastUpdate'), name(Field => 'FirstName'), name(Field => 'SurName'), name(Field => 'Notes')]) ); $rows = $sess->Database_Select($table, $fields); my @fcontacts = $rows ? map { ($_->{'tag'} = delete $_->{'notes'}) =~ s/^soocial:(\S+?)(:(\d+))?$/$1/; $_->{'updated'} = (int($3) || $_->{'updated'}); $_ } grep { $_->{'notes'} =~ /^soocial:/ } map { {id => $_->[0], updated => int(str2time($_->[1]) + 21600), first => $_->[2], last => $_->[3], notes => $_->[4] } } @{$rows} : (); my %sbt = map { $_->{'id'} => $_ } @contacts; my %fbt = map { $_->{'tag'} => $_ } @fcontacts; @contacts = grep { !exists $_->{'deleted'} } @contacts; my @deleted = grep { !exists $sbt{$_->{'tag'}} } @fcontacts; for my $i (@deleted) { my $tag = $i->{'tag'}; my $aid = $i->{'id'}; $crit = name(CritList => value([name(Crit => { Field => 'AddressId', Op => '=', Value => $aid })])); $table = name(Table => 'Contacts'); $sess->Database_Delete($table, $crit); $table = name(Table => 'Addresses'); $sess->Database_Delete($table, $crit); delete $fbt{$tag}; print "Deleted contact $i->{'first'} $i->{'last'}\n"; } for my $i (@contacts) { my $tag = $i->{'id'}; my $aid = exists $fbt{$tag} ? $fbt{$tag}->{'id'} : 0; $fvals = name( FieldValueList => value([ map { name(FieldValue => $_) } ( {Field => 'FirstName', Value => $i->{'first'}}, {Field => 'SurName', Value => $i->{'last'}}, {Field => 'Notes', Value => "soocial:$tag:" . time}, ) ]) ); my $refresh = 1; if (!$aid) { print "Inserting address $tag ($i->{'name'})\n"; $table = name(Table => 'Addresses'); $aid = $sess->Database_Insert($table, $fvals); } elsif ($sbt{$tag}->{'updated'} > $fbt{$tag}->{'updated'}) { print "Address $tag ($i->{'name'}) is updated:\n"; print " ", ctime($sbt{$tag}->{'updated'}), " (S)\n"; print " ", ctime($fbt{$tag}->{'updated'}), " (F)\n"; $crit = name(CritList => value([name(Crit => { Field => 'AddressId', Op => '=', Value => $aid })])); $table = name(Table => 'Addresses'); $sess->Database_Update($table, $fvals, $crit); $table = name(Table => 'Contacts'); my $res = $sess->Database_Delete($table, $crit); } else { $refresh = 0; } next if !$refresh; my $entry = sub { return (name(Table => 'Contacts'), name( FieldValueList => value([ map { name(FieldValue => $_) } ( {Field => 'AddressId', Value => $aid}, {Field => 'ContactType', Value => shift}, {Field => 'Details', Value => shift}, {Field => 'IsDefault', Value => shift}, ) ]) )); }; my $default = 1; for my $x (qw(home work other)) { for my $j (map { $_->[0] } grep { $_->[1] eq $x } @{$i->{'email'}}) { print " - inserting email $j (", $sess->Database_Insert($entry->(0, $j, $default)), ")\n"; $default = 0; } } $default = 0; for my $j (@{$i->{'phone'}}) { my $type = 4; $type = 1 if $j->[1] eq 'home'; $type = 2 if $j->[1] eq 'work'; $type = 3 if $j->[1] eq 'cell'; print " - inserting $j->[1] phone $j->[0] (type $type) (", $sess->Database_Insert($entry->($type, $j->[0], $default)), ")\n"; } } $sess->Logout();