facebook-google-calendar-sync 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +73 -0
- data/Rakefile +1 -0
- data/bin/facebook-google-calendar-sync +33 -0
- data/examples/.google-api.yaml +7 -0
- data/examples/crontab.txt +2 -0
- data/examples/facebook-google-calendar-sync.sh +4 -0
- data/facebook-google-calendar-sync.gemspec +29 -0
- data/lib/facebook_google_calendar_sync.rb +41 -0
- data/lib/facebook_google_calendar_sync/cli.rb +9 -0
- data/lib/facebook_google_calendar_sync/event.rb +36 -0
- data/lib/facebook_google_calendar_sync/google_calendar.rb +66 -0
- data/lib/facebook_google_calendar_sync/google_calendar_client.rb +92 -0
- data/lib/facebook_google_calendar_sync/google_calendar_description.rb +17 -0
- data/lib/facebook_google_calendar_sync/logging.rb +11 -0
- data/lib/facebook_google_calendar_sync/synchroniser.rb +101 -0
- data/lib/facebook_google_calendar_sync/version.rb +3 -0
- data/lib/time_zone_hack.rb +17 -0
- data/spec/facebook-google-calendar-sync_spec.rb +11 -0
- data/spec/google_calendar_client_spec.rb +14 -0
- data/spec/spec_helper.rb +19 -0
- metadata +207 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Beth
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
# FacebookGoogleCalendarSync
|
2
|
+
|
3
|
+
Sync Facebook Events Calendar to Google Calendar.
|
4
|
+
|
5
|
+
For Facebook and Google Calendar users
|
6
|
+
|
7
|
+
Who want to be super organised and not miss out on events or double book themselves
|
8
|
+
|
9
|
+
FacebookGoogleCalendarSync
|
10
|
+
|
11
|
+
Is a gem
|
12
|
+
|
13
|
+
That imports Facebook events into Google Calendar.
|
14
|
+
|
15
|
+
Unlike the existing "import the iCal URL provided by Facebook" solution
|
16
|
+
|
17
|
+
This gem allows the user to delete events that they are not interested in without going to Facebook to click "Not Going",
|
18
|
+
|
19
|
+
While also allowing synchronisation to be reliably and regularly scheduled* using cron or similar.
|
20
|
+
|
21
|
+
It also displays the details of the "private" Facebook events which would otherwise be hidden by Google Calendar.
|
22
|
+
|
23
|
+
*Google Calendar updates external calendars at unpredictable times, far too rarely (often more than 24 hours between updates) and doesn't allow manual refreshes. It also doesn't notify you when it has been unable to update your calendar, so you don't know when you're looking at an out of date version.
|
24
|
+
|
25
|
+
## Installation
|
26
|
+
|
27
|
+
Install the gem 'specific_install' and then install the facebook-google-calendar-sync gem directly from git (it's not on Rubygems yet):
|
28
|
+
|
29
|
+
$ gem install specific_install
|
30
|
+
$ gem specific_install -l http://github.com/bethesque/facebook-google-calendar-sync.git
|
31
|
+
|
32
|
+
Create a Goggle permissions file to allow the gem to access your Google Calendar. This command will open a browser window.
|
33
|
+
|
34
|
+
$ bundle exec google-api oauth-2-login --scope=https://www.googleapis.com/auth/calendar --client-id=436161995365.apps.googleusercontent.com --client-secret=WgTEjg-b8rXCRL28hweLcSuV
|
35
|
+
|
36
|
+
You will now have a .google-api.yaml file in your home directory.
|
37
|
+
|
38
|
+
For more information on the last step see https://developers.google.com/google-apps/calendar/firstapp#register and the Ruby tab on https://developers.google.com/google-apps/calendar/instantiate
|
39
|
+
|
40
|
+
## Usage
|
41
|
+
|
42
|
+
You can find your Facebook iCal URL by going to your Events page, and clicking the cog icon and selecting Export. Copy the URL from the "upcoming events" link, and change the "webcal://" prefix to "http://".
|
43
|
+
|
44
|
+
To run:
|
45
|
+
|
46
|
+
$ bundle exec facebook-google-calendar-sync -f "http://www.facebook.com/ical/u.php?uid=12345&key=67890"
|
47
|
+
|
48
|
+
If your Google API YAML file isn't stored at ~/.google-api.yaml, you can specify the location using the command line option "-c"
|
49
|
+
|
50
|
+
By default, your events will be synchronised to a calendar called "My Facebook Events". If this does not exist, it will be created using the timezone of your primary calendar. You can specify the name of the calendar (which may be a pre-existing one) using the command line option "-n"
|
51
|
+
|
52
|
+
## Long term usage
|
53
|
+
|
54
|
+
You will probably want to set this up as a cron job or similar. See the examples directory for more information.
|
55
|
+
|
56
|
+
## Known issues
|
57
|
+
|
58
|
+
When a Facebook event does not have a location, the time in the iCal export will be up to 2 days ahead of the actual date displayed in Facebook. This behaviour can also be observed in the Android mobile client. This may be because the timezone is incorrectly set when there is no location.
|
59
|
+
|
60
|
+
If a Facebook event has synchronised to your Google calendar then deleted, if the synchronisation process attempts (incorrectly) to add it again, Google Calendar will throw an exception saying that an event with this identifier already exists.
|
61
|
+
|
62
|
+
## Contributing
|
63
|
+
|
64
|
+
1. Fork it
|
65
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
66
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
67
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
68
|
+
5. Create new Pull Request
|
69
|
+
|
70
|
+
## TODO
|
71
|
+
|
72
|
+
* Tests....
|
73
|
+
* Work out if there is a way to fix the event date when there is no location.
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,33 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'facebook_google_calendar_sync/cli'
|
3
|
+
require 'facebook_google_calendar_sync'
|
4
|
+
require 'optparse'
|
5
|
+
require 'ostruct'
|
6
|
+
|
7
|
+
options = {}.merge(FacebookGoogleCalendarSync::DEFAULT_CONFIG)
|
8
|
+
|
9
|
+
OptionParser.new do |opts|
|
10
|
+
opts.banner = "Usage: facebook-google-calendar-sync [options]"
|
11
|
+
|
12
|
+
opts.on("-f", "--facebook-calendar-url URL", "Mandatory. Your Facebook iCalendar URL. eg. \"http://www.facebook.com/ical/u.php?uid=12345&key=67890\" Put the URL in quotes or you may find your process will be backgrounded because of the ampersand.") do | url |
|
13
|
+
options[:facebook_calendar_url] = url
|
14
|
+
end
|
15
|
+
|
16
|
+
c_help = "Optional. The location of your Google API YAML file. Defaults to #{options[:google_api_config_file]}"
|
17
|
+
opts.on("-c", "--google-api-config-file CONFIG", c_help) do | file |
|
18
|
+
options[:google_api_config_file] = file
|
19
|
+
end
|
20
|
+
|
21
|
+
g_help = "Optional. Google calendar name, defaults to '#{options[:google_calendar_name]}'. This calendar will be created if it does not already exist."
|
22
|
+
opts.on("-n", "--google-calendar-name NAME", g_help) do | name |
|
23
|
+
options[:google_calendar_name] = name
|
24
|
+
end
|
25
|
+
|
26
|
+
opts.on( '-h', '--help', 'Display this screen' ) do
|
27
|
+
puts opts
|
28
|
+
exit
|
29
|
+
end
|
30
|
+
|
31
|
+
end.parse!
|
32
|
+
|
33
|
+
FacebookGoogleCalendarSync::CLI.start options
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'facebook_google_calendar_sync/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "facebook-google-calendar-sync"
|
8
|
+
spec.version = FacebookGoogleCalendarSync::VERSION
|
9
|
+
spec.authors = ["Beth"]
|
10
|
+
spec.email = ["beth@bethesque.com"]
|
11
|
+
spec.description = %q{Syncs Facebook calendar to Google calendar}
|
12
|
+
spec.summary = %q{Syncs Facebook calendar to Google calendar}
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_dependency 'google-api-client', '~>0.6'
|
22
|
+
spec.add_dependency 'ri_cal', '~>0.8'
|
23
|
+
spec.add_dependency 'activesupport', '~>3.2'
|
24
|
+
spec.add_development_dependency 'vcr', '~>2.4'
|
25
|
+
spec.add_development_dependency 'fakeweb'
|
26
|
+
spec.add_development_dependency "bundler", "~>1.3"
|
27
|
+
spec.add_development_dependency "rake"
|
28
|
+
spec.add_development_dependency "rspec", "~>2.13"
|
29
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require "facebook_google_calendar_sync/version"
|
2
|
+
require 'open-uri'
|
3
|
+
require 'ri_cal'
|
4
|
+
require 'facebook_google_calendar_sync/logging'
|
5
|
+
require 'facebook_google_calendar_sync/synchroniser'
|
6
|
+
require 'facebook_google_calendar_sync/google_calendar'
|
7
|
+
require 'facebook_google_calendar_sync/google_calendar_client'
|
8
|
+
require 'active_support/core_ext/hash/indifferent_access'
|
9
|
+
|
10
|
+
module FacebookGoogleCalendarSync
|
11
|
+
|
12
|
+
extend Logging
|
13
|
+
|
14
|
+
DEFAULT_CONFIG = {:google_api_config_file => Pathname.new(ENV['HOME']) + '.google-api.yaml', :google_calendar_name => "My Facebook Events"}
|
15
|
+
|
16
|
+
def self.sync config
|
17
|
+
config = DEFAULT_CONFIG.merge(config).with_indifferent_access
|
18
|
+
configure_client config[:google_api_config_file]
|
19
|
+
facebook_calendar = retrieve_facebook_calendar config[:facebook_calendar_url]
|
20
|
+
google_calendar = GoogleCalendar.find_or_create_calendar config[:google_calendar_name]
|
21
|
+
logger.info "Last known Facebook event update occurred at #{google_calendar.last_known_event_update}"
|
22
|
+
Synchroniser.new(facebook_calendar, google_calendar).synchronise
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def self.configure_client google_api_config_file
|
28
|
+
GoogleCalendarClient.configure do | conf |
|
29
|
+
conf.google_api_config_file = google_api_config_file
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.retrieve_facebook_calendar url
|
34
|
+
open(url) { | response | components = RiCal.parse(response) }.first
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
|
41
|
+
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'date'
|
2
|
+
|
3
|
+
module FacebookGoogleCalendarSync
|
4
|
+
module Event
|
5
|
+
|
6
|
+
def convert_event_to_hash ical_event
|
7
|
+
{
|
8
|
+
'summary' => ical_event.summary,
|
9
|
+
'start' => date_hash(ical_event.dtstart),
|
10
|
+
'end' => date_hash(ical_event.dtend),
|
11
|
+
'iCalUID' => ical_event.uid,
|
12
|
+
'description' => ical_event.description,
|
13
|
+
'location' => ical_event.location
|
14
|
+
}
|
15
|
+
end
|
16
|
+
|
17
|
+
def merge_events google_event, facebook_event
|
18
|
+
google_event.to_hash.merge(convert_event_to_hash(facebook_event))
|
19
|
+
end
|
20
|
+
|
21
|
+
def date_of_most_recent_update facebook_events
|
22
|
+
most_recently_modified_event = facebook_events.max{ | event_a, event_b | event_a.last_modified <=> event_b.last_modified }
|
23
|
+
most_recently_modified_event.last_modified
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def date_hash date_time
|
29
|
+
if date_time.instance_of? Date
|
30
|
+
{'date' => date_time.strftime('%Y-%m-%d')}
|
31
|
+
else
|
32
|
+
{'dateTime' => date_time.strftime('%Y-%m-%dT%H:%M:%S.000%:z')}
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'facebook_google_calendar_sync/google_calendar_client'
|
2
|
+
require 'facebook_google_calendar_sync/google_calendar_description'
|
3
|
+
|
4
|
+
module FacebookGoogleCalendarSync
|
5
|
+
|
6
|
+
class GoogleCalendar
|
7
|
+
extend Logging
|
8
|
+
extend GoogleCalendarClient
|
9
|
+
include GoogleCalendarDescription
|
10
|
+
|
11
|
+
attr_accessor :details
|
12
|
+
|
13
|
+
def initialize details, data
|
14
|
+
@details = details
|
15
|
+
@data = data
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.find_or_create_calendar calendar_name
|
19
|
+
google_calendar_details = find_or_create_calendar_details calendar_name
|
20
|
+
calendar = get_calendar google_calendar_details.id
|
21
|
+
GoogleCalendar.new(google_calendar_details, calendar)
|
22
|
+
end
|
23
|
+
|
24
|
+
def id
|
25
|
+
@details.id
|
26
|
+
end
|
27
|
+
|
28
|
+
def summary
|
29
|
+
@details.summary
|
30
|
+
end
|
31
|
+
|
32
|
+
def last_known_event_update
|
33
|
+
extract_last_modified_date(description) rescue DateTime.new(0)
|
34
|
+
end
|
35
|
+
|
36
|
+
def description
|
37
|
+
@details.description
|
38
|
+
end
|
39
|
+
|
40
|
+
def events
|
41
|
+
@data.items
|
42
|
+
end
|
43
|
+
|
44
|
+
def timezone
|
45
|
+
@details.timeZone
|
46
|
+
end
|
47
|
+
|
48
|
+
def find_event_by_uid uid
|
49
|
+
events.find{ | event | event.i_cal_uid == uid }
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def self.find_or_create_calendar_details calendar_name
|
55
|
+
google_calendar_details = find_calendar_details_by_summary calendar_name
|
56
|
+
if google_calendar_details == nil
|
57
|
+
timezone = find_primary_calendar_details.timeZone
|
58
|
+
logger.info "Creating Google calendar #{calendar_name} with timezone #{timezone}"
|
59
|
+
google_calendar_details = create_calendar 'summary' => calendar_name, 'timeZone' => timezone
|
60
|
+
else
|
61
|
+
logger.info "Found existing Google calendar #{calendar_name}"
|
62
|
+
end
|
63
|
+
google_calendar_details
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'pathname'
|
3
|
+
require 'google/api_client'
|
4
|
+
require 'ostruct'
|
5
|
+
|
6
|
+
module FacebookGoogleCalendarSync
|
7
|
+
|
8
|
+
class SyncException < StandardError
|
9
|
+
end
|
10
|
+
|
11
|
+
module GoogleCalendarClient
|
12
|
+
def self.configure
|
13
|
+
@@config = OpenStruct.new
|
14
|
+
yield @@config
|
15
|
+
|
16
|
+
oauth_yaml = YAML.load_file(@@config.google_api_config_file)
|
17
|
+
@@client = Google::APIClient.new({:application_name => "Facebook to Google Calendar Sync", :application_version => "0.1.0"})
|
18
|
+
@@client.authorization.client_id = oauth_yaml["client_id"]
|
19
|
+
@@client.authorization.client_secret = oauth_yaml["client_secret"]
|
20
|
+
@@client.authorization.scope = oauth_yaml["scope"]
|
21
|
+
@@client.authorization.refresh_token = oauth_yaml["refresh_token"]
|
22
|
+
@@client.authorization.access_token = oauth_yaml["access_token"]
|
23
|
+
|
24
|
+
if @@client.authorization.refresh_token && @@client.authorization.expired?
|
25
|
+
@@client.authorization.fetch_access_token!
|
26
|
+
end
|
27
|
+
|
28
|
+
@@calendar_service = @@client.discovered_api('calendar', 'v3')
|
29
|
+
end
|
30
|
+
|
31
|
+
def find_calendar_details_by_summary calendar_summary
|
32
|
+
get_calendar_list.items.find { | calendar | calendar.summary == calendar_summary && calendar.accessRole == 'owner'}
|
33
|
+
end
|
34
|
+
|
35
|
+
def find_primary_calendar_details
|
36
|
+
get_calendar_list.items.find { | calendar | calendar.primary }
|
37
|
+
end
|
38
|
+
|
39
|
+
def get_calendar_list
|
40
|
+
make_call :api_method => calendar_service.calendar_list.list
|
41
|
+
end
|
42
|
+
|
43
|
+
def get_calendar calendar_id
|
44
|
+
make_call :api_method => calendar_service.events.list, :parameters => {'calendarId' => calendar_id}
|
45
|
+
end
|
46
|
+
|
47
|
+
def add_event calendar_id, event
|
48
|
+
make_call :api_method => calendar_service.events.insert,
|
49
|
+
:parameters => {'calendarId' => calendar_id},
|
50
|
+
:body_object => event
|
51
|
+
end
|
52
|
+
|
53
|
+
def update_event calendar_id, event_id, event
|
54
|
+
make_call :api_method => calendar_service.events.update,
|
55
|
+
:parameters => {'calendarId' => calendar_id, 'eventId' => event_id},
|
56
|
+
:body_object => event
|
57
|
+
end
|
58
|
+
|
59
|
+
def create_calendar calendar_details
|
60
|
+
make_call :api_method => calendar_service.calendars.insert,
|
61
|
+
:parameters => {},
|
62
|
+
:body_object => calendar_details
|
63
|
+
end
|
64
|
+
|
65
|
+
def update_calendar calendar_id, calendar_details
|
66
|
+
make_call :api_method => calendar_service.calendars.update,
|
67
|
+
:parameters => {'calendarId' => calendar_id},
|
68
|
+
:body_object => calendar_details
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
def make_call params
|
74
|
+
result = client.execute(params.merge(:headers => {'Content-Type' => 'application/json'}))
|
75
|
+
check_for_success result
|
76
|
+
result.data
|
77
|
+
end
|
78
|
+
|
79
|
+
def client
|
80
|
+
@@client
|
81
|
+
end
|
82
|
+
|
83
|
+
def calendar_service
|
84
|
+
@@calendar_service
|
85
|
+
end
|
86
|
+
|
87
|
+
def check_for_success result
|
88
|
+
raise SyncException.new(result.status.to_s + " " + result.body) unless result.status == 200
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'date'
|
2
|
+
|
3
|
+
module FacebookGoogleCalendarSync
|
4
|
+
module GoogleCalendarDescription
|
5
|
+
DESCRIPTION_PREFIX = "Last known Facebook event update occurred at: "
|
6
|
+
DESCRIPTION_MIDDLE = "\nFacebook last checked at: "
|
7
|
+
DESCRIPTION_SUFFIX = "\nTo ensure calendar synchronises properly, please do not modify this description."
|
8
|
+
|
9
|
+
def extract_last_modified_date description
|
10
|
+
DateTime.strptime(description[DESCRIPTION_PREFIX.size..DESCRIPTION_PREFIX.size+25])
|
11
|
+
end
|
12
|
+
|
13
|
+
def create_description last_known_event_update, now
|
14
|
+
"#{DESCRIPTION_PREFIX}#{last_known_event_update.to_s}#{DESCRIPTION_MIDDLE}#{now.to_s}#{DESCRIPTION_SUFFIX}"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
require 'facebook_google_calendar_sync/event'
|
2
|
+
require 'facebook_google_calendar_sync/google_calendar_client'
|
3
|
+
require 'facebook_google_calendar_sync/google_calendar_description'
|
4
|
+
require 'time_zone_hack'
|
5
|
+
|
6
|
+
module FacebookGoogleCalendarSync
|
7
|
+
class Synchroniser
|
8
|
+
include Event
|
9
|
+
include Logging
|
10
|
+
include GoogleCalendarDescription
|
11
|
+
include GoogleCalendarClient
|
12
|
+
|
13
|
+
def initialize(facebook_calendar, google_calendar)
|
14
|
+
@facebook_calendar = facebook_calendar
|
15
|
+
@google_calendar = google_calendar
|
16
|
+
end
|
17
|
+
|
18
|
+
def facebook_calendar
|
19
|
+
@facebook_calendar
|
20
|
+
end
|
21
|
+
|
22
|
+
def google_calendar
|
23
|
+
@google_calendar
|
24
|
+
end
|
25
|
+
|
26
|
+
def synchronise
|
27
|
+
synchronise_events
|
28
|
+
update_last_known_event_update
|
29
|
+
end
|
30
|
+
|
31
|
+
def synchronise_events
|
32
|
+
facebook_calendar.events.each do | facebook_event |
|
33
|
+
begin
|
34
|
+
synchronise_event facebook_event
|
35
|
+
rescue StandardError => e
|
36
|
+
logger.error e
|
37
|
+
logger.error "Error synchronising event. Please note that if this was a new event, it will not have been added to your calendar."
|
38
|
+
logger.error convert_event_to_hash(facebook_event)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def synchronise_event facebook_event
|
44
|
+
google_event = google_calendar.find_event_by_uid facebook_event.uid
|
45
|
+
if google_event == nil
|
46
|
+
handle_google_event_not_found facebook_event
|
47
|
+
else
|
48
|
+
handle_google_event_found facebook_event, google_event
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def handle_google_event_not_found facebook_event
|
53
|
+
if event_created_since_calendar_last_modified facebook_event
|
54
|
+
logger.info "Adding '#{facebook_event.summary}' to #{google_calendar.summary}"
|
55
|
+
add_event google_calendar.id, convert_event_to_hash(facebook_event)
|
56
|
+
else
|
57
|
+
logger.info "Not updating '#{facebook_event.summary}' as it has been deleted from the target calendar since #{google_calendar.last_known_event_update}."
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def handle_google_event_found facebook_event, google_event
|
62
|
+
if event_updated_since_calendar_last_modified facebook_event
|
63
|
+
logger.info "Updating '#{facebook_event.summary}' in #{google_calendar.summary}"
|
64
|
+
update_event google_calendar.id, google_event.id, merge_events(google_event, facebook_event)
|
65
|
+
else
|
66
|
+
logger.info "Not updating '#{facebook_event.summary}' in #{google_calendar.summary} as #{to_local(facebook_event.last_modified)} is not later than #{to_local(google_event.updated)}"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def event_updated_since_calendar_last_modified facebook_event
|
71
|
+
facebook_event.last_modified > google_calendar.last_known_event_update
|
72
|
+
end
|
73
|
+
|
74
|
+
def event_created_since_calendar_last_modified facebook_event
|
75
|
+
facebook_event.created > google_calendar.last_known_event_update
|
76
|
+
end
|
77
|
+
|
78
|
+
def date_of_most_recent_event_update
|
79
|
+
to_local(date_of_most_recent_update(facebook_calendar.events))
|
80
|
+
end
|
81
|
+
|
82
|
+
def update_last_known_event_update
|
83
|
+
last_modified = date_of_most_recent_event_update
|
84
|
+
if last_modified != google_calendar.last_known_event_update
|
85
|
+
logger.info "Updating description of '#{google_calendar.summary}' to include the time of the last known update, #{last_modified}"
|
86
|
+
details = google_calendar.details.to_hash.merge({'description' => create_description(date_of_most_recent_event_update, current_time_in_google_calendar_timezone)})
|
87
|
+
update_calendar google_calendar.id, details
|
88
|
+
else
|
89
|
+
logger.info "Not updating description of '#{google_calendar.summary}' as the date of the most recent update has not changed from #{google_calendar.last_known_event_update}."
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def current_time_in_google_calendar_timezone
|
94
|
+
to_local(DateTime.now)
|
95
|
+
end
|
96
|
+
|
97
|
+
def to_local date_or_time
|
98
|
+
date_or_time.convert_time_zone(google_calendar.timezone)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class Time
|
2
|
+
#Useful but not thread safe!!!!
|
3
|
+
def convert_time_zone(to_zone)
|
4
|
+
original_zone = ENV["TZ"]
|
5
|
+
utc_time = dup.gmtime
|
6
|
+
ENV["TZ"] = to_zone
|
7
|
+
to_zone_time = utc_time.localtime
|
8
|
+
ENV["TZ"] = original_zone
|
9
|
+
return to_zone_time
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class DateTime
|
14
|
+
def convert_time_zone(to_zone)
|
15
|
+
self.to_time.convert_time_zone(to_zone).to_datetime
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "FacebookGoogleCalendarSync::CLI" do
|
4
|
+
describe "start" do
|
5
|
+
it "should run the CLI", :vcr do
|
6
|
+
FacebookGoogleCalendarSync::CLI.start :google_calendar_name => "My Facebook Events",
|
7
|
+
:timezone => "Australia/Melbourne",
|
8
|
+
:facebook_calendar_url => 'http://www.facebook.com/ical/u.php?uid=TEST&key=TEST'
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'pathname'
|
3
|
+
|
4
|
+
describe "FacebookGoogleCalendarSync::GoogleCalendarClient" do
|
5
|
+
describe "configure" do
|
6
|
+
|
7
|
+
it "should work", :vcr do
|
8
|
+
FacebookGoogleCalendarSync::GoogleCalendarClient.configure do | config |
|
9
|
+
config.google_api_config_file = Pathname.new(ENV['HOME']) + '.google-api.yaml'
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'vcr'
|
2
|
+
require 'pathname'
|
3
|
+
|
4
|
+
lib_dir = File.join(File.dirname(__FILE__), "..", "lib")
|
5
|
+
$:.unshift(lib_dir)
|
6
|
+
|
7
|
+
require 'facebook_google_calendar_sync'
|
8
|
+
require 'facebook_google_calendar_sync/cli'
|
9
|
+
|
10
|
+
VCR.configure do |c|
|
11
|
+
c.cassette_library_dir = 'spec/cassettes'
|
12
|
+
c.hook_into :fakeweb
|
13
|
+
c.configure_rspec_metadata!
|
14
|
+
end
|
15
|
+
|
16
|
+
#For VCR
|
17
|
+
RSpec.configure do |c|
|
18
|
+
c.treat_symbols_as_metadata_keys_with_true_values = true
|
19
|
+
end
|
metadata
ADDED
@@ -0,0 +1,207 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: facebook-google-calendar-sync
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Beth
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-04-15 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: google-api-client
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0.6'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0.6'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: ri_cal
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ~>
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0.8'
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0.8'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: activesupport
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ~>
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '3.2'
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '3.2'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: vcr
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ~>
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '2.4'
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ~>
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '2.4'
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: fakeweb
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
type: :development
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: bundler
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ~>
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '1.3'
|
102
|
+
type: :development
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ~>
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '1.3'
|
110
|
+
- !ruby/object:Gem::Dependency
|
111
|
+
name: rake
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
113
|
+
none: false
|
114
|
+
requirements:
|
115
|
+
- - ! '>='
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
none: false
|
122
|
+
requirements:
|
123
|
+
- - ! '>='
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '0'
|
126
|
+
- !ruby/object:Gem::Dependency
|
127
|
+
name: rspec
|
128
|
+
requirement: !ruby/object:Gem::Requirement
|
129
|
+
none: false
|
130
|
+
requirements:
|
131
|
+
- - ~>
|
132
|
+
- !ruby/object:Gem::Version
|
133
|
+
version: '2.13'
|
134
|
+
type: :development
|
135
|
+
prerelease: false
|
136
|
+
version_requirements: !ruby/object:Gem::Requirement
|
137
|
+
none: false
|
138
|
+
requirements:
|
139
|
+
- - ~>
|
140
|
+
- !ruby/object:Gem::Version
|
141
|
+
version: '2.13'
|
142
|
+
description: Syncs Facebook calendar to Google calendar
|
143
|
+
email:
|
144
|
+
- beth@bethesque.com
|
145
|
+
executables:
|
146
|
+
- facebook-google-calendar-sync
|
147
|
+
extensions: []
|
148
|
+
extra_rdoc_files: []
|
149
|
+
files:
|
150
|
+
- .gitignore
|
151
|
+
- Gemfile
|
152
|
+
- LICENSE.txt
|
153
|
+
- README.md
|
154
|
+
- Rakefile
|
155
|
+
- bin/facebook-google-calendar-sync
|
156
|
+
- examples/.google-api.yaml
|
157
|
+
- examples/crontab.txt
|
158
|
+
- examples/facebook-google-calendar-sync.sh
|
159
|
+
- facebook-google-calendar-sync.gemspec
|
160
|
+
- lib/facebook_google_calendar_sync.rb
|
161
|
+
- lib/facebook_google_calendar_sync/cli.rb
|
162
|
+
- lib/facebook_google_calendar_sync/event.rb
|
163
|
+
- lib/facebook_google_calendar_sync/google_calendar.rb
|
164
|
+
- lib/facebook_google_calendar_sync/google_calendar_client.rb
|
165
|
+
- lib/facebook_google_calendar_sync/google_calendar_description.rb
|
166
|
+
- lib/facebook_google_calendar_sync/logging.rb
|
167
|
+
- lib/facebook_google_calendar_sync/synchroniser.rb
|
168
|
+
- lib/facebook_google_calendar_sync/version.rb
|
169
|
+
- lib/time_zone_hack.rb
|
170
|
+
- spec/facebook-google-calendar-sync_spec.rb
|
171
|
+
- spec/google_calendar_client_spec.rb
|
172
|
+
- spec/spec_helper.rb
|
173
|
+
homepage: ''
|
174
|
+
licenses:
|
175
|
+
- MIT
|
176
|
+
post_install_message:
|
177
|
+
rdoc_options: []
|
178
|
+
require_paths:
|
179
|
+
- lib
|
180
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
181
|
+
none: false
|
182
|
+
requirements:
|
183
|
+
- - ! '>='
|
184
|
+
- !ruby/object:Gem::Version
|
185
|
+
version: '0'
|
186
|
+
segments:
|
187
|
+
- 0
|
188
|
+
hash: -2766979718590756267
|
189
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
190
|
+
none: false
|
191
|
+
requirements:
|
192
|
+
- - ! '>='
|
193
|
+
- !ruby/object:Gem::Version
|
194
|
+
version: '0'
|
195
|
+
segments:
|
196
|
+
- 0
|
197
|
+
hash: -2766979718590756267
|
198
|
+
requirements: []
|
199
|
+
rubyforge_project:
|
200
|
+
rubygems_version: 1.8.25
|
201
|
+
signing_key:
|
202
|
+
specification_version: 3
|
203
|
+
summary: Syncs Facebook calendar to Google calendar
|
204
|
+
test_files:
|
205
|
+
- spec/facebook-google-calendar-sync_spec.rb
|
206
|
+
- spec/google_calendar_client_spec.rb
|
207
|
+
- spec/spec_helper.rb
|