Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix Authentication with Dockerfile + Retry mechanism + Large todo handling #19

Merged
merged 4 commits into from
Feb 14, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
FROM rcdeoliveira/gkeepapi
# Set the working directory to home folder
WORKDIR /home
# Copy necessary files into the container
COPY config.ini gkeep2notion.py requirements.txt /home/
# Set the entry point to bash
CMD ["bash"]
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,21 @@ chmod +x gkeep2notion.py

### Preventing "Authentication failed" on some systems

____
#### Running inside of a Docker Container
In order to use the `gkeepapi` dependency, you'll need to run it inside of a docker container that is compatible with it. Otherwise you'll most likely run into an "Authentication failed" error.

1. Build the Docker Image
```
docker build -t gkeep2notion_image .
```
2. Run the Docker Container
```
docker run -it --name gkeep2notion_container gkeep2notion_image /bin/bash
```
3. Now you should be inside the container and the rest of the guide should work
____

On some systems the authentication fails even with valid credentials. This may happen because of three reasons:
1. You have enabled 2FA on your account
2. Google issues a CAPTCHA for your IP address
Expand Down
89 changes: 63 additions & 26 deletions gkeep2notion.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#!/usr/local/bin/python3
from typing import List, Dict
trustmaster marked this conversation as resolved.
Show resolved Hide resolved
from argparse import ArgumentParser
from configparser import ConfigParser
from enum import Enum
Expand All @@ -9,7 +10,7 @@
import urllib.request

from gkeepapi import Keep, node
from notion_client import Client
from notion_client import Client, errors
import time


Expand Down Expand Up @@ -67,7 +68,7 @@ def _parse(self, text: str):
self.add_chunk(c)

@property
def chunks(self) -> list[dict]:
def chunks(self) -> List[Dict]:
return self._chunks

def add_chunk(self, text: str, url: str = ''):
Expand Down Expand Up @@ -101,7 +102,7 @@ def __init__(self, title: str, parent_id: str):
self._parent_id = parent_id
self._children = []

def render(self) -> dict:
def render(self) -> Dict:
return {
"properties": {
"title": [{"text": {"content": self._title}}]
Expand All @@ -114,20 +115,20 @@ def render(self) -> dict:
}

@property
def parent(self) -> dict:
def parent(self) -> Dict:
return {
"type": "page_id",
"page_id": self._parent_id
}

@property
def properties(self) -> dict:
def properties(self) -> Dict:
return {
"title": [{"text": {"content": self._title}}]
}

@property
def children(self) -> dict:
def children(self) -> Dict:
return self._children

@property
Expand Down Expand Up @@ -254,7 +255,7 @@ def downloadFile(url, path):
urllib.request.urlretrieve(url, path)


def parseBlock(p: str) -> dict:
def parseBlock(p: str) -> Dict:
"""Parses a line from a Keep Note into a Notion block type and text

Supported block types:
Expand Down Expand Up @@ -296,14 +297,14 @@ def parseTextToPage(text: str, page: Page):
page.add_text(block['text'], block['type'])


def getNoteCategories(note: node.TopLevelNode) -> list[str]:
def getNoteCategories(note: node.TopLevelNode) -> List[str]:
categories = []
for label in note.labels.all():
categories.append(label.name)
return categories


def importPageWithCategories(notion: Client, note: node.TopLevelNode, root: Page, categories: dict[str, Page]) -> Page:
def importPageWithCategories(notion: Client, note: node.TopLevelNode, root: Page, categories: Dict[str, Page]) -> Page:
# Extract categories
rootName = root.title
cats = getNoteCategories(note)
Expand Down Expand Up @@ -362,8 +363,14 @@ def parseNote(note: node.TopLevelNode, page: Page, keep: Keep, config: Config):

def parseList(list: node.List, page: Page):
item: node.ListItem
for item in list.items: # type: node.ListItem
page.add_todo(item.text, item.checked)
for item in list.items:
while item.text:
# Take the first 1500 characters or less, notion sets max todo length to 2000
# so we take 1500 to be safe in case of emojis which take more
chunk = item.text[:1500]
item.text = item.text[1500:] if len(item.text) > 1500 else ""
# Add a new to-do item with the chunk of text
page.add_todo(chunk, item.checked)


def url2uuid(url: str) -> str:
Expand Down Expand Up @@ -425,19 +432,49 @@ def url2uuid(url: str) -> str:
gnotes = keep.all()

i = 0
for gnote in gnotes:
max_retries = 3 # Retry mechanism
retry_delay = 2
while i < len(gnotes):
gnote = gnotes[i]
i += 1
if isinstance(gnote, node.List):
if not config.import_todos:
continue
print(f'Importing TODO #{i}: {gnote.title}')
page = importPageWithCategories(notion, gnote, todos, categories)
parseList(gnote, page)
create_page(notion, page)
else:
if not config.import_notes:
continue
print(f'Importing note #{i}: {gnote.title}')
page = importPageWithCategories(notion, gnote, notes, categories)
parseNote(gnote, page, keep, config)
create_page(notion, page)
try:
if isinstance(gnote, node.List):
if not config.import_todos:
continue
print(f'Importing TODO #{i}: {gnote.title}')
page = importPageWithCategories(notion, gnote, todos, categories)
parseList(gnote, page)
create_page(notion, page)
else:
if not config.import_notes:
continue
print(f'Importing note #{i}: {gnote.title}')
page = importPageWithCategories(notion, gnote, notes, categories)
parseNote(gnote, page, keep, config)
create_page(notion, page)
except errors.APIResponseError as e:
if e.code == 400:
retries = 0
while retries < max_retries:
print(f"Retrying request after {retry_delay} seconds...")
time.sleep(retry_delay)
try:
# Retry the request
if isinstance(gnote, node.List):
page = importPageWithCategories(notion, gnote, todos, categories)
parseList(gnote, page)
create_page(notion, page)
else:
page = importPageWithCategories(notion, gnote, notes, categories)
parseNote(gnote, page, keep, config)
create_page(notion, page)
break # Exit the retry loop if successful
except errors.APIResponseError as e:
if e.code == 400:
retries += 1
else:
raise # If the error is not a 400, raise it immediately
else:
raise # Raise the error if maximum retries are exceeded
else:
raise # If the error is not a 400, raise it immediately
2 changes: 2 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@ gkeepapi
keyring
notion-client>=2.0.0
requests<2.30.0
notion
trustmaster marked this conversation as resolved.
Show resolved Hide resolved
tqdm
trustmaster marked this conversation as resolved.
Show resolved Hide resolved