Files
racalmas/tools/sync_cms/lib/GoogleCalendarApi.pm
Milan 56881b92d0 rewrite Export to Google Calendar
In the past Google Calendar exports were done by first removing all
events of a day and then create new ones. This takes a lot of time for
export and runs into Google Calendar usage limits after some time.

By now content will be compared before removing/creating a single event
one. To be able to do so, all other sync sources and targets have been
removed, so its only possible to export from database to Google Calendar
by this change.

To trigger an export you need to create a trigger file.
run_jobs.pl runs periodically e.g. started by cron and checks if a
trigger file exists and start sync_cms.pl to export the selected events
to the Google Calendar. Trigger files and jobs are configured at
jobs.config. Each job has a source and target file containing the access
data for calcms and the calendar.

Configuration files have been cleaned up. Old Accounts and passwords have
been removed. They hopefully should have been not active for a long time ;-)
2018-09-22 23:09:03 +02:00

251 lines
7.2 KiB
Perl

package GoogleCalendarApi;
use strict;
use warnings;
use JSON;
use JSON::WebToken;
use LWP::UserAgent;
use HTML::Entities;
use URI::Escape;
use Data::Dumper;
use DateTime;
use Time::HiRes qw(sleep);
use Common ( 'info', 'error' );
sub new {
my $class = shift;
my $params = shift;
#print Dumper($class);
my $self = {};
for my $attr ( 'calendarId', 'debug' ) {
$self->{$attr} = $params->{$attr} if defined $params->{$attr};
}
$self->{debug} = 1;
my $instance = bless $self, $class;
if ( ( defined $params->{serviceAccount} ) && ( defined $params->{privateKey} ) ) {
$instance->login( $params->{serviceAccount}, $params->{privateKey} );
}
return $instance;
}
sub setCalendar {
my $self = shift;
my $calendarId = shift;
$self->{calendarId} = $calendarId;
}
sub getBasicUrl {
my $self = shift;
return 'https://www.googleapis.com/calendar/v3/calendars/' . encode_entities( $self->{calendarId} );
}
#https://developers.google.com/google-apps/calendar/v3/reference/events/list
#returns {
# 'timeZone' => 'Europe/Berlin',
# 'description' => "Radioprogramm von Pi Radio f\x{fc}r 88vier.de",
# 'defaultReminders' => [],
# 'accessRole' => 'owner',
# 'etag' => '"1415821582086000"',
# 'kind' => 'calendar#events',
# 'summary' => '88vier.de Pi Radio (Programm)',
# 'updated' => '2014-11-12T19:46:22.086Z',
# 'items' => [...]
# }
sub getEvents {
my $self = shift;
my $params = shift;
my $url = '/events?';
for my $param (
'iCalUID', 'alwaysIncludeEmail', 'maxAttendees', 'maxResults',
'orderBy', 'pageToken', 'privateExtendedProperty', 'q',
'sharedExtendedProperty', 'showDeleted', 'showHiddenInvitations', 'singleEvents',
'syncToken', 'timeZone'
)
{
$url .= '&' . $param . '=' . uri_escape( $params->{$param} ) if defined $params->{$param};
}
for my $param ( 'timeMin', 'timeMax', 'updatedMin' ) {
$url .= '&' . $param . '=' . uri_escape( $self->formatDateTime( $params->{$param} ) ) if defined $params->{$param};
}
my $result = $self->httpRequest( 'GET', $url );
return $result;
}
# sleep 0.25 seconds to prevent hitting the 5.0 requests/second/user rate
#sub sleep{
# my $this=shift;
# my $duration=shift;
# $duration=1 unless defined $duration;
# select(undef, undef, undef, $duration);
#}
#https://developers.google.com/google-apps/calendar/v3/reference/events/delete
sub deleteEvent {
my $self = shift;
my $eventId = shift;
my $url = '/events/' . $eventId;
#DELETE https://www.googleapis.com/calendar/v3/calendars/calendarId/events/eventId
my $result = $self->httpRequest( 'DELETE', $url );
#$self->sleep();
return $result;
}
#https://developers.google.com/google-apps/calendar/v3/reference/events/insert
sub insertEvent {
my $self = shift;
my $params = shift;
my $event = {
start => {
dateTime => $self->formatDateTime( $params->{start} )
},
end => {
dateTime => $self->formatDateTime( $params->{end} )
},
summary => $params->{summary} || '',
description => $params->{description} || '',
location => $params->{location} || '',
status => $params->{confirmed} || 'confirmed'
};
$event = encode_json $event;
#POST https://www.googleapis.com/calendar/v3/calendars/calendarId/events
my $url = '/events';
my $result = $self->httpRequest( 'POST', $url, $event );
#$self->sleep();
return $result;
}
# send a HTTP request
sub httpRequest {
my $self = shift;
my $method = shift;
my $url = shift;
my $content = shift || '';
sleep 0.3;
print STDERR "$method " . $url . "\n" if $self->{debug};
die("missing url") unless defined $url;
die("calendarId not set") unless defined $self->{calendarId};
die("not logged in ") unless defined $self->{api};
#prepend basic url including calendar id
$url = $self->getBasicUrl() . $url;
print STDERR "$method " . $url . "\n" if $self->{debug};
my $response = undef;
if ( $method eq 'GET' ) {
$response = $self->{api}->get($url);
} elsif ( ( $method eq 'POST' ) || ( $method eq 'PUT' ) ) {
#return;
print STDERR $content . "\n" if $self->{debug};
my $request = HTTP::Request->new( $method, $url );
$request->header( 'Content-Type' => 'application/json' );
$request->content($content);
$response = $self->{api}->request($request);
} elsif ( $method eq 'DELETE' ) {
#return;
$response = $self->{api}->delete($url);
}
if ( $response->is_success ) {
my $content = $response->content;
return {} if $content eq '';
return decode_json($content);
} else {
print "ERROR:\n";
print "Code: " . $response->code . "\n";
print "Message: " . $response->message . "\n";
print $response->content . "\n";
die;
}
}
# write datetime object to string
sub formatDateTime {
my $self = shift;
my $dt = shift;
my $datetime = $dt->format_cldr("yyyy-MM-ddTHH:mm:ssZZZZZ");
print STDERR "$dt -> $datetime\n" if $self->{debug};
return $datetime;
}
# parse datetime from string to object
sub getDateTime {
my $self = shift;
my $datetime = shift;
my $timezone = shift;
return if ( !defined $datetime ) or ( $datetime eq '' );
my @l = split /[\-\;T\s\:\+\.]/, $datetime;
$datetime = DateTime->new(
year => $l[0],
month => $l[1],
day => $l[2],
hour => $l[3],
minute => $l[4],
second => $l[5],
time_zone => $timezone
);
return $datetime;
}
# login with serviceAccount and webToken (from privateKey)
sub login {
my $self = shift;
my $serviceAccount = shift;
my $privateKey = shift;
# https://developers.google.com/accounts/docs/OAuth2ServiceAccount
my $time = time;
#create JSON Web Token
my $jwt = JSON::WebToken->encode(
{
iss => $serviceAccount,
scope => 'https://www.googleapis.com/auth/calendar',
aud => 'https://accounts.google.com/o/oauth2/token',
exp => $time + 3600,
iat => $time,
},
$privateKey,
'RS256',
{ typ => 'JWT' }
);
#send JSON web token to authentication service
$self->{auth} = LWP::UserAgent->new();
my $response = $self->{auth}->post(
'https://accounts.google.com/o/oauth2/token',
{
grant_type => encode_entities('urn:ietf:params:oauth:grant-type:jwt-bearer'),
assertion => $jwt
}
);
die( $response->code, "\n", $response->content, "\n" ) unless $response->is_success();
my $data = decode_json( $response->content );
#create a new user agent and set token to bearer
$self->{api} = LWP::UserAgent->new();
$self->{api}->default_header( Authorization => 'Bearer ' . $data->{access_token} );
print STDERR "login successful\n" if $self->{debug};
return $data;
}
1;