Skip to content

Commit

Permalink
Add automatic installation
Browse files Browse the repository at this point in the history
  • Loading branch information
Ravio1i committed Oct 23, 2021
1 parent fab3936 commit 367aee0
Show file tree
Hide file tree
Showing 24 changed files with 2,477 additions and 728 deletions.
10 changes: 9 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,18 @@ jobs:

steps:
- uses: actions/checkout@v2
- name: Set up Python 3.9
- name: Install Python 3.9
uses: actions/setup-python@v2
with:
python-version: 3.9
- name: Install Python Poetry
uses: abatilo/actions-poetry@v2.1.0
with:
poetry-version: 1.1.2
- name: Configure poetry
shell: bash
run: python -m poetry config virtualenvs.in-project true

- name: Install dependencies
run: |
python -m pip install --upgrade pip
Expand Down
11 changes: 9 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Create and publish a Docker image
name: Release

on:
push:
Expand All @@ -9,7 +9,7 @@ env:
IMAGE_NAME: ${{ github.repository }}

jobs:
build-and-push-image:
docker:
runs-on: ubuntu-latest
permissions:
contents: read
Expand Down Expand Up @@ -39,3 +39,10 @@ jobs:
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}

pip:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
ref: ${{ github.event.inputs.branch }}
18 changes: 3 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,13 @@

Notion-GCal-Sync is a python application to bidirectional synchronize calendar events within notion and google calendar.

## Setup
## Getting started

With [pip](https://pypi.org/project/notion-gcal-sync/)

```bash
pip install notion-gcal-sync
```

With docker

```yaml
docker pull notion-gcal-sync
```

Keep following [these instructions](docs/setup.md).
Follow [these instructions](https://github.com/Ravio1i/notion-gcal-sync/blob/main/docs/setup.md).

## Usage

**IMPORTANT:** Make sure you followed the [setup](docs/setup.md) and configured the `config.yml` with your notion token and page for Notion API and gathered and setup credentials `client_secret.json` for Google Calendar API.
**IMPORTANT:** Make sure you followed the [setup](https://github.com/Ravio1i/notion-gcal-sync/blob/main/docs/setup.md) and configured the `config.yml` with your notion token and page for Notion API and gathered and setup credentials `client_secret.json` for Google Calendar API.

From pip and running directly

Expand Down
9 changes: 6 additions & 3 deletions docs/setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,19 @@ This document describes how to set up and configure your notion-gcal-sync.

## Installation

With [pip](https://pypi.org/project/notion-gcal-sync/)

```bash
pip install notion-gcal-sync
```

With docker (Not the mounting of `client_secret.json` and `config.yml`)
With [docker](https://github.com/Ravio1i/notion-gcal-sync/pkgs/container/notion-gcal-sync)

```yaml
docker pull notion-gcal-sync
```yaml
docker pull ghrc.io/ravio1i/notion-gcal-sync
```


## Configuration

Additionally, to installing the python package you will need to properly configure and authenticate. This requires the 2 files:
Expand Down
5 changes: 5 additions & 0 deletions environment.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
name: notion-gcal-sync
channels:
- defaults
dependencies:
- python==3.9
1 change: 1 addition & 0 deletions notion_gcal_sync/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
__version__ = "1.0.0"
24 changes: 17 additions & 7 deletions notion_gcal_sync/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,26 @@
Invoke as `notion-gcal-sync' or `python -m notion-gcal-sync'
or 'python -m notion_gcal_sync.__main__
"""
import os
import logging

current_dir = os.path.dirname(__file__)
from notion_gcal_sync.install import configure

logging.basicConfig(
level=logging.INFO,
format='%(asctime)s [%(levelname)s] %(message)s',
handlers=[
logging.FileHandler('debug.log'),
logging.StreamHandler()
]
)


def main():
from notion_gcal_sync.core import main
main()
from notion_gcal_sync.core import sync

cfg = configure()
sync(cfg)

if __name__ == '__main__':
import sys
sys.exit(main())

if __name__ == "__main__":
main()
84 changes: 48 additions & 36 deletions notion_gcal_sync/clients/GCalClient.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,36 +8,40 @@
from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials

from ..config import Config
from ..events.GCalEvent import GCalEvent
from notion_gcal_sync.config import Config
from notion_gcal_sync.events.GCalEvent import GCalEvent

current_dir = os.path.dirname(__file__)
root_dir = os.path.dirname(current_dir)
CONFIG_PATH = os.path.join(os.path.expanduser("~"), ".notion-gcal-sync")


class GCalClient:
"""The client class used to perform requests against google api"""

def __init__(self, cfg: Config):
self.cfg = cfg
self.scopes = ['https://www.googleapis.com/auth/calendar']
self.credentials = self.get_credentials()
self.service = build("calendar", "v3", credentials=self.credentials, cache_discovery=False)
self.calendar = self.service.gcal_calendars().get(calendarId=self.cfg.gcal_default_calendar_id).execute()

@staticmethod
def get_credentials():
scopes = ["https://www.googleapis.com/auth/calendar"]
credentials = None
token_path = os.path.join(root_dir, 'token.json')
if os.path.exists(os.path.join(root_dir, token_path)):
credentials = Credentials.from_authorized_user_file(token_path, self.scopes)
token_path = os.path.join(CONFIG_PATH, "token.json")
if os.path.exists(os.path.join(CONFIG_PATH, token_path)):
credentials = Credentials.from_authorized_user_file(token_path, scopes)
# If there are no (valid) credentials available, let the user log in.
if not credentials or not credentials.valid:
if credentials and credentials.expired and credentials.refresh_token:
credentials.refresh(Request())
else:
credentials_path = os.path.join(root_dir, 'client_credentials.json')
flow = InstalledAppFlow.from_client_secrets_file(credentials_path, self.scopes)
credentials_path = os.path.join(CONFIG_PATH, "client_credentials.json")
flow = InstalledAppFlow.from_client_secrets_file(credentials_path, scopes)
credentials = flow.run_local_server(port=0)
# Save the credentials for the next run
with open(token_path, 'w') as token:
with open(token_path, "w") as token:
token.write(credentials.to_json())

self.service = build('calendar', 'v3', credentials=credentials, cache_discovery=False)
self.calendar = self.service.calendars().get(calendarId=self.cfg.default_calendar_id).execute()
return credentials

def get_event(self, gcal_calendar_id: str, gcal_event_id: str):
"""
Expand All @@ -57,40 +61,45 @@ def list_events(self, calendar_id: str) -> List[dict]:
max_results = 2500
gcal_event_items = []
gcal_event_count = 0
logging.info('Fetching events from calendar: {}'.format(self.cfg.get_calendar_name(calendar_id)))
logging.info("Fetching events from calendar: {}".format(self.cfg.get_calendar_name(calendar_id)))
while True:
gcal_events_res = self.service.events().list(
calendarId=calendar_id, pageToken=page_token, timeZone=self.cfg.time.timezone_name, maxResults=max_results
).execute()
gcal_event_count += len(gcal_events_res['items'])
print('Found {} events'.format(gcal_event_count), end='\r')
for event in gcal_events_res['items']:
if event['status'] == 'cancelled':
logging.debug('Event "{}" is cancelled. Skipping...'.format(event.get('id', '')))
gcal_events_res = (
self.service.events()
.list(
calendarId=calendar_id, pageToken=page_token, timeZone=self.cfg.time.timezone_name, maxResults=max_results,
)
.execute()
)
gcal_event_count += len(gcal_events_res["items"])
print("Found {} events".format(gcal_event_count), end="\r")
for event in gcal_events_res["items"]:
if event["status"] == "cancelled":
logging.debug('Event "{}" is cancelled. Skipping...'.format(event.get("id", "")))
continue
if not event.get('summary'):
logging.error('Event "{}" at "{}" does not have a name. Skipping...'
.format(event.get('id', ''), event['start']))
if not event.get("summary"):
logging.error(
'Event "{}" at "{}" does not have a name. Skipping...'.format(event.get("id", ""), event["start"])
)
continue
if event.get('recurrence'):
logging.debug('Event {} is recurrent source .Skipping...'.format(event['summary']))
if event.get("recurrence"):
logging.debug("Event {} is recurrent source .Skipping...".format(event["summary"]))
continue

gcal_event = GCalEvent.from_api(event, self.cfg, self.cfg.time)
if gcal_event.gcal_calendar_id == 'skip':
logging.debug('Event {} id is not valid. Skipping...'.format(event['summary']))
if gcal_event.gcal_calendar_id == "skip":
logging.debug("Event {} id is not valid. Skipping...".format(event["summary"]))
continue

if gcal_event.recurrent_event:
logging.debug('Using gcal event link for "{}" as recurrence reference'.format(gcal_event.name))
gcal_res = self.get_event(gcal_event.gcal_calendar_id, gcal_event.gcal_event_id)
gcal_event.recurrent_event = gcal_res['htmlLink']
gcal_event_items.append(gcal_event.dict_from_class())
page_token = gcal_events_res.get('nextPageToken')
gcal_event.recurrent_event = gcal_res["htmlLink"]
gcal_event_items.append(gcal_event.to_dict())
page_token = gcal_events_res.get("nextPageToken")
if not page_token:
break

logging.info('Found {} events from calendar: {}'.format(gcal_event_count, self.cfg.get_calendar_name(calendar_id)))
logging.info("Found {} events from calendar: {}".format(gcal_event_count, self.cfg.get_calendar_name(calendar_id)))
return gcal_event_items

def create_event(self, gcal_event: GCalEvent):
Expand All @@ -112,8 +121,11 @@ def update_event(self, gcal_event: GCalEvent) -> dict or None:
if gcal_event.read_only:
logging.info('Not updating in gcal read only event "{}"'.format(gcal_event.name))
return
return self.service.events().update(calendarId=gcal_event.gcal_calendar_id, eventId=gcal_event.gcal_event_id,
body=gcal_event.body()).execute()
return (
self.service.events()
.update(calendarId=gcal_event.gcal_calendar_id, eventId=gcal_event.gcal_event_id, body=gcal_event.body(),)
.execute()
)
# TODO: what to do about forbidden
# except:
# return None
Expand Down
Loading

0 comments on commit 367aee0

Please sign in to comment.