diff --git a/lib/Sendinblue/API.pm b/lib/Sendinblue/API.pm index 6ad8279..a3b3555 100644 --- a/lib/Sendinblue/API.pm +++ b/lib/Sendinblue/API.pm @@ -105,8 +105,168 @@ sub add_contact { } else { warn "Post $uri failed: " . $response->status_line; return {}; + } +} + +# manage lists: +sub delete_from_list { + my ($self, $list_id, $emails) = @_; + + return if !$emails || !@$emails; + + my $uri = $self->_construct_uri("contacts/lists/$list_id/contacts/remove"); + my @elist = @$emails; + my @sublist = (); + my $response; + if (scalar @$emails > 100) { + my $total = scalar @$emails; + while (@elist) { + @sublist = splice(@elist, 0, 99); + $response = $self->ua->post( + $uri, + Content => encode_json( + { + emails => \@sublist, + }), + %{ $self->headers }, + ); + sleep 1; + } + } else { + $response = $self->ua->post( + $uri, + Content => encode_json( + { + emails => $emails, + }), + %{ $self->headers }, + ); + } + if($response->is_success) { + if ($response->code == 201) { + return 1; + } else { + return decode_json $response->decoded_content; + } + } else { + warn "Post $uri failed: " . $response->status_line; + return {}; + } +} + +sub add_to_list { + my ($self, $list_id, $emails) = @_; + + return if !$emails || !@$emails; + + my $uri = $self->_construct_uri("contacts/lists/$list_id/contacts/add"); + my @elist = @$emails; + my @sublist = (); + my $response; + if (scalar @$emails > 100) { + my $total = scalar @$emails; + while (@elist) { + @sublist = splice(@elist, 0, 99); + $response = $self->ua->post( + $uri, + Content => encode_json( + { + emails => \@sublist, + }), + %{ $self->headers }, + ); + sleep 1; + } + } else { + $response = $self->ua->post( + $uri, + Content => encode_json( + { + emails => $emails, + }), + %{ $self->headers }, + ); + } + + if($response->is_success) { + if ($response->code == 201) { + return 1; + } else { + return decode_json $response->decoded_content; + } + } else { + warn "Post $uri failed: " . $response->status_line; + return {}; + } +} + +# New API replaces bulk/batch update? +# One call per set of ListIds? (cant attach listids to contacts, its now +# list of contacts for.. set of listids +# Cant do unlinkListIds, that only seems to be available on individual contact +# update now.. :( +# https://developers.brevo.com/reference/import-contacts + +sub import_contacts { + my ($self, $contacts, $list_ids) = @_; + + return if !@$contacts; + # no modifiedAt / createdAt + # only one of email/id/sms + foreach my $c (@$contacts) { + delete $c->{modifiedAt}; + delete $c->{createdAt}; + # delete $c->{email}; + delete $c->{sms}; + } + + my $uri = $self->_construct_uri('contacts/import'); + print STDERR Dumper($contacts); + + my @clist = @$contacts; + my @sublist = @$contacts; + my $response; + if (scalar @$contacts > 100) { + my $total = scalar @$contacts; + while (@clist) { + @sublist = splice(@clist, 0, 99); + + $response = $self->ua->post( + $uri, + Content => encode_json( + { + # updateExistingContacts => defaults to true + # body, list of objs with email and attributes + jsonBody => \@sublist, + listIds => $list_ids, #lists to add to + }), + %{ $self->headers }, + ); +# print STDERR "Update Contacts:", Dumper( $response); + sleep 1; + } + } else { + $response = $self->ua->post( + $uri, + Content => encode_json( + { + jsonBody => $contacts, + listIds => $list_ids, #lists to add to + }), + %{ $self->headers }, + ); +# print STDERR "Update Contacts:", Dumper( $response); + } + if($response->is_success) { + if ($response->code == 204) { + return 1; + } else { + return decode_json $response->decoded_content; + } + } else { + warn "Post $uri failed: " . $response->status_line; + return {}; } - } sub update_contacts { @@ -164,7 +324,7 @@ sub delete_contact { my $uri = $self->_construct_uri('contacts/' . $contact->{id}); my $response = $self->ua->delete($uri, %{ $self->headers }); - if ($response->is_sucess) { + if ($response->is_success) { return 1; } else { warn "Delete $uri failed: " . $response->status_line; diff --git a/script/sendinblue.pl b/script/sendinblue.pl index d627e87..2cf26e3 100644 --- a/script/sendinblue.pl +++ b/script/sendinblue.pl @@ -45,6 +45,9 @@ # Filter just in case we have others: my @people = grep { any {$_ == $all_people_id} (@{$_->{listIds}}) } (@$contacts); print "Found " . scalar @people . " people in All People\n"; +print "Found " . scalar @$contacts . " people in Contacts\n"; + +my %contacts_check = map { $_->{email} && lc $_->{email} => 1 } @$contacts; # Compare to db people: my $db_people = $schema->resultset('Person')->search_rs({} ); @@ -53,62 +56,113 @@ my @updates = (); my @checked_emails = (); -foreach my $person (@people) { - my $db_person = $db_people->search({ '-and' => [ - end_date => undef, - \['LOWER(email) = ?', lc($person->{email})], # - ]}); -# email => { '-ilike' => $person->{email} }, end_date => undef }); +# gather: people in list: +my $add_valid = []; +my $rem_valid = []; +my $add_news_events = []; +my $rem_news_events = []; + +# Bulk update all with "all people" list, update/delete from single lists for the rest? + +foreach my $person (@$contacts) { + my $db_person = $db_people->search(\['LOWER(email) = ?', lc($person->{email})]); # +# my $db_person = $db_people->search({ '-and' => [ +# end_date => undef, +# \['LOWER(email) = ?', lc($person->{email})], # +# ]}); +# # email => { '-ilike' => $person->{email} }, end_date => undef }); + # if ($person->{email} =~ /hunter/) { + # $DB::single=1; + # } if ($db_person->count == 1) { $db_person = $db_person->first; - if (($person->{attributes}{NAME} ne $db_person->name - || ($person->{attributes}{OPT_IN} != $db_person->opt_in) - || (($person->{attributes}{JOINED} || '') ne $db_person->created_date->date) - || ($db_person->valid_until && $person->{attributes}{PAID_UP_UNTIL} ne $db_person->real_expiry($localconfig{overlap_days})->date))) { + # delete $person->{listIds}; + # if (($person->{attributes}{NAME} ne $db_person->name + # || ($person->{attributes}{OPT_IN} != $db_person->opt_in) + # || (($person->{attributes}{JOINED} || '') ne $db_person->created_date->date) + # || ($db_person->valid_until && $person->{attributes}{PAID_UP_UNTIL} ne $db_person->real_expiry($localconfig{overlap_days})->date))) { #change list prefs: # turned on opt-in, add events+newsletter - if (!$person->{attributes}{OPT_IN} && $db_person->opt_in) { - push @{ $person->{listIds} }, $events_id, $newsletter_id; + if ($db_person->opt_in && !grep { $_ == $newsletter_id} @{$person->{listIds}}) { + push @$add_news_events, $person->{email}; } # turned off opt-in, remove em again - if ($person->{attributes}{OPT_IN} && !$db_person->opt_in) { - $person->{listIds} = [ grep { $_ != $events_id && $_ != $newsletter_id } (@{ $person->{listIds} }) ]; + if (!$db_person->opt_in && grep { $_ == $newsletter_id} @{$person->{listIds}}) { + push @$rem_news_events, $person->{email}; } # no longer a paid-up member print STDERR $db_person->name, Data::Dumper::Dumper($person->{listIds}); - if (!$db_person->valid_until || $db_person->real_expiry($localconfig{overlap_days}) < DateTime->now) { - $person->{listIds} = [ grep { $_ != $valid_all_id } (@{ $person->{listIds} }) ]; - print STDERR $db_person->name, "List Ids changed ", Data::Dumper::Dumper($person->{listIds}); - push @{$person->{unlinkListIds}}, $valid_all_id; + if ((!$db_person->valid_until || $db_person->real_expiry($localconfig{overlap_days}) < DateTime->now) && grep { $_ == $valid_all_id} @{$person->{listIds}}) { + push @$rem_valid, $person->{email}; + # print STDERR $db_person->name, "List Ids changed ", Data::Dumper::Dumper($person->{listIds}); + # push @{$person->{unlinkListIds}}, $valid_all_id; } # is a paid-up member again - if ($db_person->valid_until && $db_person->real_expiry($localconfig{overlap_days}) >= DateTime->now) { - push @{ $person->{listIds} }, $valid_all_id; - print STDERR $db_person->name, "List Ids changed again?", Data::Dumper::Dumper($person->{listIds}); + if (($db_person->valid_until && $db_person->real_expiry($localconfig{overlap_days}) >= DateTime->now) && !grep { $_ == $valid_all_id} @{$person->{listIds}}) { + push @$add_valid, $person->{email}; + # print STDERR $db_person->name, "List Ids changed again?", Data::Dumper::Dumper($person->{listIds}); } - + + if (!$db_person->end_date && + ($person->{attributes}{NAME} ne $db_person->name + || ($person->{attributes}{OPT_IN} != $db_person->opt_in) + || (($person->{attributes}{JOINED} || '') ne $db_person->created_date->date) + || ($db_person->valid_until && $person->{attributes}{PAID_UP_UNTIL} ne $db_person->real_expiry($localconfig{overlap_days})->date))) { $person->{attributes}{NAME} = $db_person->name; $person->{attributes}{JOINED} = $db_person->created_date->date; $person->{attributes}{OPT_IN} = $db_person->opt_in ? JSON::true : JSON::false; if ($db_person->real_expiry($localconfig{overlap_days})) { $person->{attributes}{PAID_UP_UNTIL} = $db_person->real_expiry($localconfig{overlap_days})->date; } - push @updates, $person; } + } else { + # If can't find the email in our db then presumably they changed their + # Email, remove from any lists + foreach my $lId (@{$person->{listIds}}) { + if ($lId == $valid_all_id) { + push @$rem_valid, $person->{email}; + } + # if ($lId == $events_id) { + # push @$rem_news_events, $person->{email}; + # } + } } push @checked_emails, $person->{email}; } # All changed members: print "Updating " . scalar @updates . " people statuses in Sendinblue\n"; # print STDERR Dumper(\@updates); -$sb->update_contacts(\@updates); +if (@updates) { + $sb->import_contacts(\@updates, [$all_people_id]); +} +# remove from news_events/valid: +if (@$rem_news_events) { + $sb->delete_from_list($events_id, $rem_news_events); + $sb->delete_from_list($newsletter_id, $rem_news_events); +} +if (@$rem_valid) { + $sb->delete_from_list($valid_all_id, $rem_valid); +} +# add to news,events,valid: +if (@$add_news_events) { + $sb->add_to_list($events_id, $add_news_events); + $sb->add_to_list($newsletter_id, $add_news_events); +} +if (@$add_valid) { + $sb->add_to_list($valid_all_id, $add_valid); +} # New members: -my $new_members = $db_people->search({ email => { '-not_in' => [map { lc($_) } @checked_emails] }, end_date => undef }); +#my @not_in = map { lc($_) } @checked_emails; +# Don't add any contacts that are already in the system +my @not_in = keys %contacts_check; +my $new_members = $db_people->search({ email => { '-not_in' => \@not_in }, end_date => undef }); print "Adding " . $new_members->count . " new members into Sendinblue\n"; while (my $person = $new_members->next) { + # not_in is case sensitive, sigh + next if $contacts_check{lc $person->email}; $sb->add_contact({ email => $person->email, listIds => [ $all_people_id, ($person->opt_in ? ($events_id, $newsletter_id) : ()), ( $person->valid_until && $person->valid_until >= DateTime->now ? ($valid_all_id) : ()) ], @@ -130,7 +184,6 @@ print "Would remove " . scalar @gone_people . " members who left from Sendinblue\n"; # print STDERR Dumper(\@gone_people); -exit; foreach my $person (@gone_people) { $sb->delete_contact($person); }