Skip to content

Commit

Permalink
readme
Browse files Browse the repository at this point in the history
  • Loading branch information
Swaggeroo committed Jan 7, 2024
1 parent 65af4ab commit abfba45
Show file tree
Hide file tree
Showing 3 changed files with 127 additions and 38 deletions.
6 changes: 5 additions & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ services:
internet-monitor:
image: ghcr.io/swaggeroo/gkeepbringsync
restart: unless-stopped
volumes:
- ./token.txt:/opt/gkeepbringsync/token.txt # Google Auth token keep it safe it's like your password
- ./list.txt:/opt/gkeepbringsync/list.txt
environment:
# Google
- GOOGLE_EMAIL=<your email>
Expand All @@ -17,4 +20,5 @@ services:

# OPTIONAL
#- SYNC_MODE=0 # 0 = bidirectional, 1 = bring master, 2 = google master
#- TIMEOUT=60 # minutes
#- TIMEOUT=60 # minutes
#- BRING_LIST_NAME=Groceries
31 changes: 22 additions & 9 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,25 @@ A sample Dockerfile is provided. You can also run it directly with Python (Only
## Usage
You need to provide the following environment variables:
### Environment variables
| Variable | Description | Default | Required |
|-------------------|------------------------------------------------------------------------------------------------------|---------|----------|
| `GOOGLE_EMAIL` | Your Google account email address | | **Yes** |
| `GOOGLE_PASSWORD` | Your Google account password - [App Password](https://myaccount.google.com/apppasswords) recommended | | **Yes** |
| `KEEP_LIST_ID` | Your Google Note ID (visible in the URL when selecting a Note in the webapp) | | **Yes** |
| `BRING_EMAIL` | Your Bring account email address | | **Yes** |
| `BRING_PASSWORD` | Your Bring account password | | **Yes** |
| `SYNC_MODE` | 0 = bidirectional, 1 = bring master, 2 = google master | 0 | No |
| `TIMEOUT` | Timeout between syncs in *minutes* | 60 | No |
| Variable | Description | Default | Required |
|-------------------|-----------------------------------------------------------------------------------------------------------------------|------------------------------|----------|
| `GOOGLE_EMAIL` | Your Google account email address | | **Yes** |
| `GOOGLE_PASSWORD` | Your Google account password - [App Password](https://myaccount.google.com/apppasswords) recommended | | **Yes** |
| `KEEP_LIST_ID` | Your Google Note ID (visible in the URL when selecting a Note in the webapp) | | **Yes** |
| `BRING_EMAIL` | Your Bring account email address | | **Yes** |
| `BRING_PASSWORD` | Your Bring account password | | **Yes** |
| `SYNC_MODE` | 0 = bidirectional, 1 = bring master, 2 = google master | 0 | No |
| `TIMEOUT` | Timeout between syncs in *minutes* \| 0 = only run once (with the provided docker-compose it will restart infinitely) | 60 | No |
| `BRING_LIST_NAME` | Name of your Bring List | Using first list in Response | No |

### Sync modes
| Mode | Description |
|------|-------------------------------------------------------------------------------------------------------|
| 0 | Bidirectional sync. Changes in Google Keep will be reflected in Bring and vice versa. |
| 1 | Bring master. Changes in Bring will be reflected in Google Keep. Google Keep changes will be ignored. |
| 2 | Google master. Changes in Google Keep will be reflected in Bring. Bring changes will be ignored. |

### Please note
- The token.txt file is used to store the Google Auth token. It is created automatically. You can delete it at any time to force a new login. Keep it safe as it can be used to access your Google account.
- I didn't tested expiration of the token yet. If it expires, the script will probably crash. At the next run it should delete the token.txt and crash again. After that it should work again. With docker this should be no problem as the container will be restarted automatically.
- At first run the script will take the keep and bring lists and merge them. After that it will only sync changes.
128 changes: 100 additions & 28 deletions src/app.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,29 @@
import os
import os
import gkeepapi
import schedule
import time
from python_bring_api.bring import Bring
from datetime import datetime

# Constants
GOOGLE_EMAIL = os.environ['GOOGLE_EMAIL']
GOOGLE_PASSWORD = os.environ['GOOGLE_PASSWORD']
BRING_EMAIL = os.environ['GOOGLE_EMAIL']
BRING_PASSWORD = os.environ['BRING_PASSWORD']
KEEP_LIST_ID = os.environ['KEEP_LIST_ID']
SYNC_MODE = int(os.environ.get('SYNC_MODE', "0")) # 0 = bidirectional, 1 = bring master, 2 = google master
TIMEOUT = int(os.environ.get('TIMEOUT', "60")) # in minutes
# BRING_LIST_NAME

# init services
gkeepapi.node.DEBUG = True
keep = gkeepapi.Keep()
bring = Bring(BRING_EMAIL, BRING_PASSWORD)


def login():
"""
Logs into the Bring and Google Keep services.
"""
bring.login()

if os.path.exists('token.txt'):
Expand All @@ -42,12 +46,49 @@ def login():


def delete_old_items(note):
"""
Deletes all checked items from the provided Google Keep note.
:param note: The Google Keep note to delete items from.
"""
for item in note.checked:
print('Deleting item: ' + item.text)
item.delete()


def get_keep_list_item(name, keep_list):
"""
Returns the unchecked item with the provided name from the Google Keep list.
If no such item exists, it returns None.
:param name: The name of the item to get.
:param keep_list: The Google Keep list to get the item from.
:return: The unchecked item with the provided name, or None if no such item exists.
"""
for item in keep_list.unchecked:
if item.text == name:
return item
return None


def delete_duplicates(keep_list):
"""
Deletes duplicate items from the provided Google Keep list.
:param keep_list: The Google Keep list to delete duplicates from.
"""
items = getAllItemsKeep(keep_list)
for item in items:
if items.count(item) > 1:
print('Deleting duplicate item: ' + item)
get_keep_list_item(item, keep_list).delete()
items.remove(item)


def get_bring_list(lists):
"""
Returns the Bring list that matches the name provided in the environment variable 'BRING_LIST_NAME'.
If 'BRING_LIST_NAME' is not set, it returns the first list.
:param lists: The list of Bring lists.
:return: The selected Bring list.
"""
if os.environ.get('BRING_LIST_NAME') is not None:
for bring_list in lists:
if bring_list['name'] == os.environ.get('BRING_LIST_NAME'):
Expand All @@ -56,18 +97,38 @@ def get_bring_list(lists):


def getAllItemsBring(bring_list):
"""
Returns all items in the provided Bring list.
:param bring_list: The Bring list to get items from.
:return: A list of all items in the Bring list.
"""
items = bring.getItems(bring_list['listUuid'])
return [item['name'] for item in items['purchase']]


def getAllItemsKeep(note):
return [item.text for item in note.unchecked]
def getAllItemsKeep(keep_list):
"""
Returns all unchecked items in the provided Google Keep note.
:param keep_list: The Google Keep note to get items from.
:return: A list of all unchecked items in the Google Keep note.
"""
return [item.text for item in keep_list.unchecked]


def sync(keep_list, bring_list):
"""
Synchronizes the provided Google Keep and Bring lists
based on the sync mode set in the environment variable 'SYNC_MODE'.
:param keep_list: The Google Keep list to synchronize.
:param bring_list: The Bring list to synchronize.
"""
print('Syncing lists ' + str(datetime.now()))
keep.sync()

delete_old_items(keep_list)
delete_duplicates(keep_list)
keep.sync()

bring_items = getAllItemsBring(bring_list)
keep_items = getAllItemsKeep(keep_list)

Expand Down Expand Up @@ -101,6 +162,10 @@ def sync(keep_list, bring_list):


def load_cached_list():
"""
Loads the cached list from a file.
Returns the list if it exists, otherwise returns None.
"""
if os.path.exists('list.txt'):
with open('list.txt', 'r', encoding="utf-8") as f:
keep_list = f.read().split('\n')
Expand All @@ -111,12 +176,35 @@ def load_cached_list():


def save_list(new_list):
"""
Saves the provided list to a file.
:param new_list: The list to save.
"""
with open('list.txt', 'w', encoding="utf-8") as f:
f.write('\n'.join(new_list))
f.close()


def apply_list(new_list, bring_list, keep_list):
"""
Applies the provided list to the Google Keep and Bring lists.
:param new_list: The list to apply.
:param bring_list: The Bring list to apply the new list to.
:param keep_list: The Google Keep list to apply the new list to.
"""
"""
bring_items = getAllItemsBring(bring_list)
keep_items = getAllItemsKeep(keep_list)
for item in set(bring_items) - set(new_list):
bring.removeItem(bring_list['listUuid'], item.encode('utf-8').decode('ISO-8859-9'))
for item in set(new_list) - set(bring_items):
bring.saveItem(bring_list['listUuid'], item.encode('utf-8').decode('ISO-8859-9'))
for item in set(keep_items) - set(new_list):
[i for i in keep_list.unchecked if i.text == item][0].delete()
for item in set(new_list) - set(keep_items):
keep_list.add(item.encode('utf-8').decode('ISO-8859-9'), False, gkeepapi.node.NewListItemPlacementValue.Bottom)
"""

# bring
bring_items = getAllItemsBring(bring_list)
for item in bring_items:
Expand All @@ -140,22 +228,7 @@ def apply_list(new_list, bring_list, keep_list):
keep_list.add(item.encode('utf-8').decode('ISO-8859-9'), False, gkeepapi.node.NewListItemPlacementValue.Bottom)


def delete_duplicates(keep_list):
items = getAllItemsKeep(keep_list)
for item in items:
if items.count(item) > 1:
print('Deleting duplicate item: ' + item)
get_keep_list_item(item, keep_list).delete()
items.remove(item)


def get_keep_list_item(name, keep_list):
for item in keep_list.unchecked:
if item.text == name:
return item
return None


# Main
print('Starting app')
print('Sync mode: ' + str(SYNC_MODE))
print('Timeout: ' + str(TIMEOUT) + ' minutes')
Expand All @@ -165,17 +238,16 @@ def get_keep_list_item(name, keep_list):
# load Keep
keep.sync()
keepList = keep.get(KEEP_LIST_ID)
delete_old_items(keepList)
delete_duplicates(keepList)
keep.sync()
print('Keep list: ' + keepList.title)

# load Bring
bringList = get_bring_list(bring.loadLists()['lists'])

sync(keepList, bringList)

print('Starting scheduler')
schedule.every(TIMEOUT).minutes.do(sync, keepList, bringList)
while True:
schedule.run_pending()
time.sleep(1)
if TIMEOUT != 0:
print('Starting scheduler run every ' + str(TIMEOUT) + ' minutes')
schedule.every(TIMEOUT).minutes.do(sync, keepList, bringList)
while True:
schedule.run_pending()
time.sleep(1)

0 comments on commit abfba45

Please sign in to comment.