-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 0aedbdb
Showing
8 changed files
with
3,783 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider | ||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 | ||
|
||
|
||
.idea/** | ||
|
||
# Node | ||
###################### | ||
/node/ | ||
node_tmp/ | ||
node_modules/ | ||
npm-debug.log.* | ||
/.awcache/* | ||
.node-gyp/ | ||
/.cache-loader/* | ||
|
||
###################### | ||
# Intellij | ||
###################### | ||
.idea/ | ||
*.iml | ||
*.iws | ||
*.ipr | ||
*.ids | ||
*.orig | ||
classes/ | ||
.IntelliJIdea*/ | ||
.local/share/JetBrains/ | ||
.java/ | ||
out/ | ||
|
||
###################### | ||
# NPM | ||
###################### | ||
*.npm/ | ||
*.cache/ | ||
*.config/ | ||
*.yarnrc | ||
*.coverage/ | ||
|
||
##### | ||
# ESLint | ||
###################### | ||
.eslintcache | ||
/out | ||
|
||
# compiled output | ||
/dist | ||
/tmp | ||
/out-tsc | ||
# Only exists if Bazel was run | ||
/bazel-out | ||
|
||
# dependencies | ||
*/node_modules | ||
|
||
# profiling files | ||
chrome-profiler-events*.json | ||
speed-measure-plugin*.json | ||
|
||
# IDEs and editors | ||
/.idea | ||
.project | ||
.classpath | ||
.c9/ | ||
*.launch | ||
.settings/ | ||
*.sublime-workspace | ||
|
||
|
||
google_calendar_key.json | ||
config.json | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
.idea | ||
|
||
config.json | ||
google_calendar_key.json |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
# google-calendar-sync | ||
|
||
Sync your private google calendar with your work google calendar. | ||
Details of the event will be left out - it will only be visible as 'Private event' in your work calendar. | ||
|
||
|
||
## Setup | ||
- Create a google cloud project (https://console.cloud.google.com/) | ||
- Create a service account (https://console.cloud.google.com/iam-admin/serviceaccounts/create) | ||
- Download a key for the service account (as JSON) and place it in `google_calendar_key.json` | ||
- Create a file `config.json` and add the source and target calendar adresses | ||
- Add the email of your service account (`client_email` within your downloaded key) to both the source and target calendar | ||
- For the source calendar, read-only access is sufficient | ||
- For the target calendar, write access is required (your administrator might have to enable this in the Google Admin console) | ||
- Add a unique id to each source-target combination (this is used when multiple sources sync into the same target) | ||
- `futureDays` defaults to `14`, `pastDays` to `7`. | ||
- Example config: | ||
```json | ||
[ | ||
{ | ||
"id": "sync-id-1", | ||
"sourceCalendar": "[email protected]", | ||
"targetCalendar": "[email protected]", | ||
"futureDays": 180, | ||
"pastDays": 14 | ||
} | ||
] | ||
``` | ||
- Run it with `npx -y @cirqusde/google-calendar-sync` | ||
|
||
## Publish to npm | ||
- Bump version in `package.json` | ||
- Run `npm install` | ||
- Run `npm publish` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,154 @@ | ||
#!/usr/bin/node | ||
import { calendar_v3, google } from 'googleapis'; | ||
import { JWT } from 'google-auth-library/build/src/auth/jwtclient'; | ||
import { readFileSync } from 'fs'; | ||
import { SourceTargetConfiguration } from './models'; | ||
import moment = require('moment'); | ||
|
||
const calendar: calendar_v3.Calendar = google.calendar('v3'); | ||
|
||
const key = JSON.parse(readFileSync('google_calendar_key.json', 'utf8')); | ||
|
||
|
||
const scopes = ['https://www.googleapis.com/auth/calendar']; | ||
const auth: JWT = new google.auth.JWT(key.client_email, undefined, key.private_key, scopes, undefined); | ||
|
||
const syncCalendar = async (): Promise<{ | ||
createCounter: number, | ||
updateCounter: number, | ||
removeCounter: number, | ||
}> => { | ||
let createCounter = 0; | ||
let updateCounter = 0; | ||
let removeCounter = 0; | ||
|
||
const config: SourceTargetConfiguration[] = JSON.parse(readFileSync('config.json', 'utf8')); | ||
|
||
for (const configEntry of config) { | ||
const pastDays = configEntry.pastDays || 7; | ||
const futureDays = configEntry.futureDays || 14; | ||
|
||
const earliestDate = moment(new Date()).subtract(pastDays, 'days'); | ||
const latestDate = moment(new Date()).add(futureDays, 'days'); | ||
|
||
|
||
const sourceEventsResponse = await calendar.events.list({ | ||
auth, | ||
calendarId: configEntry.sourceCalendar, | ||
timeMin: earliestDate.toISOString(), | ||
timeMax: latestDate.toISOString(), | ||
}); | ||
const sourceEvents = sourceEventsResponse.data.items || []; | ||
|
||
const targetEventsResponse = await calendar.events.list({ | ||
auth, | ||
calendarId: configEntry.targetCalendar, | ||
timeMin: earliestDate.toISOString(), | ||
timeMax: latestDate.toISOString(), | ||
}); | ||
const targetEvents = targetEventsResponse.data.items; | ||
|
||
let eventsToRemove = targetEvents?.filter(t => t.description?.includes(`google-sync-calendar-config-id: ${configEntry.id}`)) || []; | ||
|
||
for (const sourceEvent of sourceEvents) { | ||
const sourceEventId = sourceEvent.id || sourceEvent.iCalUID; | ||
if (!sourceEventId) { | ||
console.warn(`Skipping event ${sourceEvent.summary} because no id is available`); | ||
continue; | ||
} | ||
|
||
if (sourceEvent.status === 'cancelled') { | ||
// Will be removed later, if it was synced before | ||
continue; | ||
} | ||
|
||
const targetEvent = targetEvents.filter(t => t.description?.includes(`google-sync-calender-source-id: ${sourceEventId}`))[0]; | ||
if (!targetEvent) { | ||
try { | ||
await createEvent(sourceEvent, sourceEventId, configEntry); | ||
createCounter++; | ||
} catch (e) { | ||
console.error('Error while creating event', e); | ||
} | ||
continue; | ||
} | ||
|
||
// Event still exists -> Should not be removed | ||
eventsToRemove = eventsToRemove.filter(t => t.description !== targetEvent.description); | ||
|
||
if (areEventsEqual(sourceEvent, targetEvent)) { | ||
// Nothing to do | ||
continue; | ||
} | ||
|
||
// Something got changed | ||
try { | ||
await updateEvent(sourceEvent, sourceEventId, targetEvent, configEntry); | ||
updateCounter++; | ||
} catch (e) { | ||
console.error('Error while updating event', e); | ||
} | ||
} | ||
|
||
// Remove events which no longer exist in source | ||
for (const eventToRemove of eventsToRemove) { | ||
try { | ||
await calendar.events.delete({ | ||
auth, | ||
calendarId: configEntry.targetCalendar, | ||
eventId: eventToRemove.id || eventToRemove.iCalUID, | ||
}); | ||
removeCounter++; | ||
} catch (e) { | ||
console.error('Error while deleting event', e); | ||
} | ||
} | ||
} | ||
|
||
return { | ||
createCounter, | ||
updateCounter, | ||
removeCounter, | ||
}; | ||
} | ||
|
||
const areEventsEqual = (a: calendar_v3.Schema$Event, b: calendar_v3.Schema$Event): boolean => { | ||
return (a.start?.date === b.start?.date && a.start?.dateTime === b.start?.dateTime && a.start?.timeZone === b.start?.timeZone) && | ||
(a.end?.date === b.end?.date && a.end?.dateTime === b.end?.dateTime && a.end?.timeZone === b.end?.timeZone); | ||
} | ||
|
||
const createEvent = async (sourceEvent: calendar_v3.Schema$Event, sourceEventId: string, configEntry: SourceTargetConfiguration): Promise<void> => { | ||
await calendar.events.insert({ | ||
auth, | ||
calendarId: configEntry.targetCalendar, | ||
requestBody: getRequestBody(sourceEvent, sourceEventId, configEntry), | ||
}); | ||
} | ||
|
||
const updateEvent = async (sourceEvent: calendar_v3.Schema$Event, sourceEventId: string, targetEvent: calendar_v3.Schema$Event, configEntry: SourceTargetConfiguration): Promise<void> => { | ||
await calendar.events.update({ | ||
auth, | ||
calendarId: configEntry.targetCalendar, | ||
eventId: targetEvent.id || targetEvent.iCalUID, | ||
requestBody: getRequestBody(sourceEvent, sourceEventId, configEntry), | ||
}); | ||
} | ||
|
||
const getRequestBody = (sourceEvent: calendar_v3.Schema$Event, sourceEventId: string, configEntry: SourceTargetConfiguration) => { | ||
return { | ||
summary: 'Private event', | ||
description: `This is a private event synced by google-calendar-sync. | ||
Do not update/remove this event manually - changes will be overwritten! | ||
google-sync-calender-source-id: ${sourceEventId} | ||
google-sync-calendar-config-id: ${configEntry.id} | ||
`, | ||
start: sourceEvent.start, | ||
end: sourceEvent.end, | ||
} | ||
} | ||
|
||
syncCalendar().then((res) => { | ||
console.log(`Everything synced (${res.createCounter} created, ${res.updateCounter} updated, ${res.removeCounter} removed)`); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
export interface SourceTargetConfiguration { | ||
id: string; | ||
sourceCalendar: string; | ||
targetCalendar: string; | ||
futureDays?: number; | ||
pastDays?: number; | ||
} |
Oops, something went wrong.