├── LICENSE ├── Notion2GCal Python Video Example.mp4 ├── Notion2GCalSync - Public.py └── README.md /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 akarri2001 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Notion2GCal Python Video Example.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akarri2001/Notion-to-Google-Calendar/27d3a50b95be3fd53d2cc294bd578e1cdbbe7cca/Notion2GCal Python Video Example.mp4 -------------------------------------------------------------------------------- /Notion2GCalSync - Public.py: -------------------------------------------------------------------------------- 1 | import os 2 | from notion_client import Client 3 | from pprint import pprint 4 | from datetime import datetime, timedelta, date 5 | import time 6 | from googleapiclient.discovery import build 7 | from google_auth_oauthlib.flow import InstalledAppFlow 8 | import pickle 9 | 10 | NOTION_TOKEN = #the secret_something 11 | database_id = #get the mess of numbers before the "?" on your dashboard URL and then split it into 8-4-4-4-12 characters between each dash 12 | notion_time = datetime.now().strftime("%Y-%m-%dT%H:%M:%S-04:00") #has to be adjusted for when daylight savings is different 13 | #^^ This is for America/New York when it's daylight savings 14 | 15 | urlRoot = #open up a task and then copy the URL root up to the "p=" 16 | GCalTokenLocation = #This is the command you will be feeding into the command prompt to run the GCalToken program 17 | 18 | #GCal Set Up Part 19 | calendarID = #The GCal calendar id. The format is something like "sldkjfliksedjgodsfhgshglsj@groups.calendar.google.com" 20 | credentialsLocation = #This is where you keep the pickle file that has the Google Calendar Credentials 21 | 22 | 23 | 24 | os.environ['NOTION_TOKEN'] = NOTION_TOKEN 25 | notion = Client(auth=os.environ["NOTION_TOKEN"]) 26 | 27 | 28 | 29 | 30 | # Query the database for tasks that are for today or in the next week and not in ToDoIst 31 | todayDate = datetime.today().strftime("%Y-%m-%d") 32 | 33 | my_page = notion.databases.query( 34 | **{ 35 | "database_id": database_id, 36 | "filter": { 37 | "and": [ 38 | { 39 | "property": "On GCal?", 40 | "checkbox": { 41 | "equals": False 42 | } 43 | }, 44 | { 45 | "or": [ 46 | { 47 | "property": "Date", 48 | "date": { 49 | "equals": todayDate 50 | } 51 | }, 52 | { 53 | "property": "Date", 54 | "date": { 55 | "next_week": {} 56 | } 57 | } 58 | ] 59 | } 60 | ] 61 | }, 62 | } 63 | ) 64 | 65 | 66 | #Make list that contains each of the results 67 | resultList = [] 68 | 69 | # print(my_page.json().keys()) 70 | results = my_page.json()['results'] 71 | UTCTime = datetime.now() + timedelta(hours = 5) 72 | 73 | for result in results: 74 | resultList.append(result) 75 | 76 | pageId = result['id'] 77 | 78 | my_page = notion.pages.update( ##### THIS CHECKS OFF THAT THE TASK IS PUSHED OVER TO TODOIST 79 | **{ 80 | "page_id": pageId, 81 | "properties": { 82 | 'On GCal?': { 83 | "checkbox": True 84 | }, 85 | 'Last Updated Time': { 86 | "date":{ 87 | 'start': notion_time, #has to be adjsuted for when daylight savings is different 88 | 'end': None, 89 | } 90 | } 91 | }, 92 | }, 93 | ) 94 | 95 | TaskNames = [] 96 | Dates = [] 97 | Initiatives = [] 98 | ExtraInfo = [] 99 | URL_list = [] 100 | 101 | # print(resultList[0]['properties']['Task'].keys()) 102 | 103 | def makeTaskURL(ending, urlRoot): 104 | urlId = ending[0:8] + ending[9:13] + ending[14:18] + ending[19:23] + ending[24:] 105 | return urlRoot + urlId 106 | 107 | if len(resultList) > 0: 108 | 109 | for el in resultList: 110 | print('\n') 111 | print(el) 112 | print('\n') 113 | 114 | TaskNames.append(el['properties']['Task']['title'][0]['text']['content']) 115 | Dates.append(el['properties']['Date']['date']['start']) 116 | try: 117 | Initiatives.append(el['properties']['Initiative']['select']['name']) 118 | except: 119 | Initiatives.append("") 120 | 121 | try: 122 | ExtraInfo.append(el['properties']['Extra Info']['rich_text'][0]['text']['content']) 123 | except: 124 | ExtraInfo.append("") 125 | URL_list.append(makeTaskURL(result['id'], urlRoot)) 126 | 127 | print(TaskNames) 128 | print(Dates) 129 | print(Initiatives) 130 | print(ExtraInfo) 131 | print(URL_list) 132 | 133 | else: 134 | print("Nothing new added to GCal") 135 | 136 | 137 | #note down the last time that the code was run 138 | lastEditTime = datetime.now() + timedelta(hours = 5) 139 | timeStr = lastEditTime.strftime("%Y-%m-%dT%H:%M:00.000Z") 140 | text_file = open("Last_GCal_Update_Time.txt", "w") 141 | n = text_file.write(timeStr) 142 | text_file.close() 143 | 144 | 145 | 146 | 147 | #SET UP THE GOOGLE CALENDAR API INTERFACE 148 | 149 | credentials = pickle.load(open(credentialsLocation, "rb")) 150 | service = build("calendar", "v3", credentials=credentials) 151 | 152 | try: 153 | calendar = service.calendars().get(calendarId=calendarID).execute() 154 | except: 155 | #refresh the token 156 | import os 157 | os.system(GCalTokenLocation) 158 | 159 | #SET UP THE GOOGLE CALENDAR API INTERFACE 160 | 161 | credentials = pickle.load(open(credentialsLocation, "rb")) 162 | service = build("calendar", "v3", credentials=credentials) 163 | 164 | # result = service.calendarList().list().execute() 165 | # print(result['items'][:]) 166 | 167 | calendar = service.calendars().get(calendarId=calendarID).execute() 168 | 169 | print(calendar) 170 | 171 | ###################################################################### 172 | #METHOD TO MAKE A CALENDAR EVENT 173 | 174 | def makeCalEvent(eventName, eventDescription, eventStartTime, sourceURL): 175 | eventStartTime = datetime.combine(eventStartTime, datetime.min.time()) + timedelta(hours=8) ##make the events pop up at 8 am instead of 12 am 176 | eventEndTime = eventStartTime + timedelta(hours =1) 177 | timezone = 'America/New_York' 178 | event = { 179 | 'summary': eventName, 180 | 'description': eventDescription, 181 | 'start': { 182 | 'dateTime': eventStartTime.strftime("%Y-%m-%dT%H:%M:%S"), 183 | 'timeZone': timezone, 184 | }, 185 | 'end': { 186 | 'dateTime': eventEndTime.strftime("%Y-%m-%dT%H:%M:%S"), 187 | 'timeZone': timezone, 188 | }, 189 | 'source': { 190 | 'title': 'Notion Link', 191 | 'url': sourceURL, 192 | } 193 | } 194 | print('Adding this event to calendar: ', eventName) 195 | x = service.events().insert(calendarId=calendarID, body=event).execute() 196 | return x['id'] 197 | 198 | 199 | def makeEventDescription(initiative, info): 200 | return f'Initiative: {initiative} \n{info}' 201 | 202 | ################### 203 | 204 | 205 | ### Create events for tasks that have not been in GCal already 206 | calEventIdList = [] 207 | for i in range(len(TaskNames)): 208 | calEventId = makeCalEvent(TaskNames[i], makeEventDescription(Initiatives[i], ExtraInfo[i]), datetime.strptime(Dates[i], '%Y-%m-%d'), URL_list[i]) 209 | calEventIdList.append(calEventId) 210 | 211 | print(calEventIdList) 212 | i = 0 213 | for result in resultList: 214 | pageId = result['id'] 215 | 216 | my_page = notion.pages.update( ##### THIS CHECKS OFF THAT THE TASK IS PUSHED OVER TO GCAL 217 | **{ 218 | "page_id": pageId, 219 | "properties": { 220 | 'GCal Event Id': { 221 | "rich_text": [{ 222 | 'text': { 223 | 'content': calEventIdList[i] 224 | } 225 | }] 226 | } 227 | }, 228 | }, 229 | ) 230 | i += 1 231 | 232 | for result in results: 233 | resultList.append(result) 234 | 235 | pageId = result['id'] 236 | 237 | my_page = notion.pages.update( ##### THIS CHECKS OFF THAT THE TASK IS PUSHED OVER TO TODOIST 238 | **{ 239 | "page_id": pageId, 240 | "properties": { 241 | 'On GCal?': { 242 | "checkbox": True 243 | }, 244 | 'Last Updated Time': { 245 | "date":{ 246 | 'start': notion_time, 247 | 'end': None, 248 | } 249 | } 250 | }, 251 | }, 252 | ) 253 | 254 | ############################### 255 | ##################################### 256 | ################################## 257 | ###### Filter events that have been updated since the GCal event has been made 258 | 259 | my_page = notion.databases.query( 260 | **{ 261 | "database_id": database_id, 262 | "filter": { 263 | "and": [ 264 | { 265 | "property": "NeedGCalUpdate", 266 | "formula":{ 267 | "checkbox": { 268 | "equals": True 269 | } 270 | } 271 | }, 272 | { 273 | "or": [ 274 | { 275 | "property": "Date", 276 | "date": { 277 | "equals": todayDate 278 | } 279 | }, 280 | { 281 | "property": "Date", 282 | "date": { 283 | "next_week": {} 284 | } 285 | } 286 | ] 287 | } 288 | ] 289 | }, 290 | } 291 | ) 292 | 293 | 294 | #Make list that contains each of the results 295 | resultList = [] 296 | 297 | # print(my_page.json().keys()) 298 | results = my_page.json()['results'] 299 | # UTCTime = datetime.now() + timedelta(hours = 5) 300 | 301 | updatingPageIds = [] 302 | updatingCalEventIds = [] 303 | for result in results: 304 | resultList.append(result) 305 | 306 | pageId = result['id'] 307 | updatingPageIds.append(pageId) 308 | print('\n') 309 | print(result) 310 | print('\n') 311 | calId = result['properties']['GCal Event Id']['rich_text'][0]['text']['content'] 312 | print(calId) 313 | updatingCalEventIds.append(calId) 314 | 315 | 316 | #Delete the event using the calendarEvent Id through for loop 317 | 318 | for CalId in updatingCalEventIds: 319 | service.events().delete(calendarId=calendarID, eventId= CalId).execute() 320 | 321 | 322 | #Make new event 323 | 324 | TaskNames = [] 325 | Dates = [] 326 | Initiatives = [] 327 | ExtraInfo = [] 328 | URL_list = [] 329 | 330 | if len(resultList) > 0: 331 | 332 | for el in resultList: 333 | print('\n') 334 | print(el) 335 | print('\n') 336 | 337 | TaskNames.append(el['properties']['Task']['title'][0]['text']['content']) 338 | Dates.append(el['properties']['Date']['date']['start']) 339 | try: 340 | Initiatives.append(el['properties']['Initiative']['select']['name']) 341 | except: 342 | Initiatives.append("") 343 | 344 | try: 345 | ExtraInfo.append(el['properties']['Extra Info']['rich_text'][0]['text']['content']) 346 | except: 347 | ExtraInfo.append("") 348 | URL_list.append(makeTaskURL(result['id'], urlRoot)) 349 | 350 | print(TaskNames) 351 | print(Dates) 352 | print(Initiatives) 353 | print(ExtraInfo) 354 | print(URL_list) 355 | 356 | else: 357 | print("Nothing new added to GCal") 358 | calEventIdList = [] 359 | 360 | for i in range(len(TaskNames)): 361 | calEventId = makeCalEvent(TaskNames[i], makeEventDescription(Initiatives[i], ExtraInfo[i]), datetime.strptime(Dates[i], '%Y-%m-%d'), URL_list[i]) 362 | calEventIdList.append(calEventId) 363 | 364 | 365 | 366 | # Update the notion dashboard based off of the new calendar event id 367 | 368 | i=0 369 | for result in results: 370 | resultList.append(result) 371 | 372 | pageId = result['id'] 373 | 374 | my_page = notion.pages.update( ##### THIS CHECKS OFF THAT THE TASK IS PUSHED OVER TO GCAL 375 | **{ 376 | "page_id": pageId, 377 | "properties": { 378 | 'GCal Event Id': { 379 | "rich_text": [{ 380 | 'text': { 381 | 'content': calEventIdList[i] 382 | } 383 | }] 384 | }, 385 | 'Last Updated Time': { 386 | "date":{ 387 | 'start': notion_time, #has to be adjsuted for when daylight savings is different 388 | 'end': None, 389 | } 390 | } 391 | }, 392 | }, 393 | ) 394 | i += 1 395 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Notion-to-Google-Calendar (1 way) 2 | Import Notion Tasks to Google Calendar 3 | 4 | NO MORE UPDATES WILL BE MADE TO THIS REPO. Attention has been put on a 2-way sync between Google Calendar and Notion: https://github.com/akarri2001/Notion-and-Google-Calendar-2-Way-Sync 5 | 6 | https://www.youtube.com/watch?v=j1mh0or2CX8 This video was used to make the Google Calendar token. Note that the library names that they installed are outdated so look at the python file to see what you'll actually need to pip install onto your computer. 7 | 8 | The Google Calendar token is what allows for the python code to access your Google Calendar and communicate with the Google Calendar servers to add/receive/modify data. 9 | 10 | You'll need to make your GCal token before setting up the rest of the Python script 11 | --------------------------------------------------------------------------------