-
Notifications
You must be signed in to change notification settings - Fork 8
/
ical-to-gcal
187 lines (151 loc) · 6.03 KB
/
ical-to-gcal
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
#!/usr/bin/env perl
# ical-to-gcal
# A quick script to fetch and parse an iCal/vcal feed, and update a named Google
# Calendar.
#
# See the README in the repo for more details on why and how to use it:
# https://github.com/bigpresh/ical-to-google-calendar
#
# David Precious <davidp@preshweb.co.uk>
use strict;
use Net::Google::Calendar;
use Net::Netrc;
use iCal::Parser;
use LWP::Simple;
use Getopt::Long;
use Digest::MD5;
my ($calendar, $ical_url);
Getopt::Long::GetOptions(
"calendar|c=s" => \$calendar,
"ical_url|ical|i=s" => \$ical_url,
) or die "Failed to parse options";
if (!$ical_url) {
die "An iCal URL must be provided (--ical_url=....)";
}
if (!$calendar) {
die "You must specify the calendar name (--calendar=...)";
}
my $ical_data = LWP::Simple::get($ical_url)
or die "Failed to fetch $ical_url";
my $ic = iCal::Parser->new;
my $ical = $ic->parse_strings($ical_data)
or die "Failed to parse iCal data";
# Hash the feed URL, so we can use it along with the event ID to uniquely
# identify events - and when removing events that aren't in the feed any more,
# remove only those which came from this feed, if the user is using multiple
# feeds. (10 characters of the hash should be enough to be reliable enough.)
my $feed_url_hash = substr(Digest::MD5->md5_hex($ical_url), 0, 10);
# We get events keyed by year, month, day - we just want a flat list of events
# to walk through. Do this keyed by the event ID, so that multiple-day events
# are handled appropriately. We'll want this hash anyway to do a pass through
# all events on the Google Calendar, removing any that are no longer in the
# iCal feed.
my %ical_events;
for my $year (keys %{ $ical->{events} }) {
for my $month (keys %{ $ical->{events}{$year} }) {
for my $day (keys %{ $ical->{events}{$year}{$month} }) {
for my $event_uid (keys %{ $ical->{events}{$year}{$month}{$day} }) {
$ical_events{ $event_uid }
= $ical->{events}{$year}{$month}{$day}{$event_uid};
}
}
}
}
# Right, we can now walk through each event in $events
for my $event_uid (keys %ical_events) {
my $event = $ical_events{$event_uid};
printf "$event_uid (%s at %s)\n",
@$event{ qw (SUMMARY LOCATION) };
}
# Get our login details, and find the Google calendar in question:
my $mach = Net::Netrc->lookup('calendar.google.com')
or die "No login details for calendar.google.com in ~/.netrc";
my ($user, $pass) = $mach->lpa;
my $gcal = Net::Google::Calendar->new;
$gcal->login($user, $pass)
or die "Google Calendar login failed";
my ($desired_calendar) = grep { $_->title eq $calendar } $gcal->get_calendars;
if (!$desired_calendar) {
die "No calendar named $calendar found!";
}
$gcal->set_calendar($desired_calendar);
# Fetch all events from this calendar, parse out the ical feed's UID and whack
# them in a hash keyed by the UID; if that UID no longer appears in the ical
# feed, it's one to delete.
my %gcal_events;
gcal_event:
for my $event ($gcal->get_events) {
my ($ical_feed_hash, $ical_uid)
= $event->content->body =~ m{\[ical_imported_uid:(.+)/(.+)\]};
# If there's no ical uid, we presumably didn't create this, so leave it
# alone
if (!$ical_uid) {
# Special-case, though: previous versions of this script didn't store
# the feed hash, so if we have only the event UID, assume it was this
# feed so the script continues working
if ($ical_uid
= $event->content->body =~ m{\[ical_imported_uid:(.+)\]}
) {
$ical_feed_hash = $feed_url_hash;
} else {
warn sprintf "Event %s (%s) ignored as it has no "
. "ical_imported_uid property",
$event->id,
$event->title;
next gcal_event;
}
}
# OK, if the event isn't for this feed, let it be:
if ($ical_feed_hash ne $feed_url_hash) {
next gcal_event;
}
# OK, if this event didn't appear in the iCal feed, it has been deleted at
# the other end, so we should delete it from our Google calendar:
if (!$ical_events{$ical_uid}) {
printf "Deleting event %s (%s) (no longer found in iCal feed)\n",
$event->id, $event->title;
$gcal->delete_entry($event)
or warn "Failed to delete an event from Google Calendar";
}
# Now check for any differences, and update if required
# Remember that we found this event, so we can refer to it when looking for
# events we need to create
$gcal_events{$ical_uid} = $event;
}
# Now, walk through the ical events we found, and create/update Google Calendar
# events
for my $ical_uid (keys %ical_events) {
my ($method, $gcal_event);
my $ical_event = $ical_events{$ical_uid};
my $gcal_event = ical_event_to_gcal_event(
$ical_event, $gcal_events{$ical_uid}
);
my $method = exists $gcal_events{$ical_uid}
? 'update_entry' : 'add_entry';
$gcal->$method($gcal_event)
or warn "Failed to $method for $ical_uid";
}
# Given an iCal event hash (from iCal::Parser) (and possibly a
# Net::Google::Calender event object to update), return an appropriate Google
# Calendar event object (either the one given, after updating it, or a newly
# populated one)
# Note: does not actually add/update the event on the calendar, just returns the
# event object.
sub ical_event_to_gcal_event {
my ($ical_event, $gcal_event) = @_;
if (ref $ical_event ne 'HASH') {
die "Given invalid iCal event";
}
if (defined $gcal_event && (!blessed($gcal_event) ||
!$gcal_event->isa('Net::Google::Calendar::Event')))
{
die "Given invalid Google Calendar event - what is it?";
}
$gcal_event ||= Net::Google::Calendar::Event->new;
my $ical_uid = $ical_event->{UID};
$gcal_event->title( $ical_event->{SUMMARY} );
$gcal_event->location( $ical_event->{LOCATION} );
$gcal_event->when( $ical_event->{DTSTART}, $ical_event->{DTEND} );
$gcal_event->content("[ical_imported_uid:$feed_url_hash/$ical_uid]");
return $gcal_event;
}