Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
3 changes: 3 additions & 0 deletions .codacy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
---
exclude_paths:
- "**/test/**"
2 changes: 2 additions & 0 deletions .flake8
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[flake8]
extend-ignore = F401, F811
26 changes: 18 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
# Habitica+Todoist

## __:warning: Alpha script!__
# Project Hype-Berry

### __:warning: Alpha script!__
⚠️ Only one way syncing is working right now.
##
This is intended to be a two-way sync of Habitica and Todoist. Any tasks that can't be found in both services should appear on the others, with the same status. If you complete a task on one service, it should appear as completed on another. Tasks that are created on Habitica should be sent to the 'Inbox' project on Todoist.

AS A NOTE: in order to have two way syncing, you MUST have a paid copy of Todoist. It's not possible for me to port complete tasks from Todoist otherwise. If you do not have a paid copy of Todoist, the following will happen:
AS A NOTE: in order to have two way syncing, you MUST have a paid copy of Todoist. It's not possible to sync complete tasks from Todoist otherwise. If you do not have a paid copy of Todoist, the following will happen:

1. Completed tasks will not sync between the services.
2. Tasks that you begin and complete from one service to the other will not transfer between the two.
Expand All @@ -13,15 +14,22 @@ That means that if you create a task in Todoist and then check it off, right now

## INSTALLATION

There are a number dependencies you'll need to install, and the commands to install them are as follows:
### Linux Installation
1. Install the python dependencies:
```
pip install todoist_api_python requests scriptabit tzlocal iso8601 python-dateutil
```
Finally, you need to add your API tokens to the `Project_Hype-Berry/source/auth.cfg.example` file. You can find your Habitica API User ID and API key by visiting https://habitica.com/user/settings/api while logged in, and your Todoist API token can be found by visiting https://todoist.com/prefs/integrations while logged in. Once you've added these tokens, you should rename the file to `Project_Hype-Berry/source/auth.cfg` (remove the '.example' at the end).
2. Get the source code [here](https://github.com/programmerPhysicist/Project_Hype-Berry/tags)
3. You need to add your API tokens to the **Project_Hype-Berry/source/auth.cfg.example** file
* To get the _Habitica API User ID_ and _API key_ goto <https://habitica.com/user/settings/api> while logged in
* To get the _Todoist API token_ goto <https://todoist.com/prefs/integrations> while logged in.
4. Rename the file to **Project_Hype-Berry/source/auth.cfg** (remove the '.example' at the end).
5. Add the folder oneWaySync to $XDG_STATE_HOME: `mkdir $XDG_STATE_HOME/oneWaySync`
6. Add the **oneWaySync.sh** script to Crontab

## TASK DIFFICULTY

I originally felt that it would be good if task difficulty translated between tasks created on Todoist and Habitica. Therefore, task difficulty should sync with the following code by default, as laid out in `main.py`
I originally felt that it would be good if task difficulty translated between tasks created on Todoist and Habitica. Therefore, task difficulty should sync with the following code by default, as laid out in **main.py**

Todoist priority | Habitica difficulty
---------------- | -------------------
Expand All @@ -30,12 +38,14 @@ p2 | Medium
p3 | Easy
p4 | Easy

If you'd like to change how the sync interprets difficulty or priority, please edit `main.py`. For example, my personal setup actually includes translating Todoist p4 to Easy, rather than Trivial, because I find that Trivial yields so few rewards they aren't worth it to me.
If you'd like to change how the sync interprets difficulty or priority, please edit **main.py**. For example, my personal setup actually includes translating Todoist p4 to Easy, rather than Trivial, because I find that Trivial yields so few rewards they aren't worth it to me.

## USAGE

Try running `python one_way_sync.py` in your terminal. (You have to run the command from the same directory that auth.cfg exists in).

Or you can try the provided shell script **oneWaySync.sh** under **Project_Hype-Berry/scripts/**

## Credit

This program is a hard fork of [Habitica-Todo](https://github.com/eringiglio/Habitica-todo), with some fixes added. Habitica-Todo has been abandoned by its original author.
Expand Down
2 changes: 1 addition & 1 deletion projectHypeBerry.egg-info/PKG-INFO
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Metadata-Version: 1.0
Name: Project_Hype-Berry
Version: 2.1.0
Version: 2.1.1
Summary: An API app for syncing todoist and habitica tasks
Home-page: https://github.com/programmerPhysicist/Project_Hype-Berry
Author: UNKNOWN
Expand Down
2 changes: 2 additions & 0 deletions pytest.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[pytest]
pythonpath = source test/fixtures
3 changes: 3 additions & 0 deletions scripts/debugTests.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/bin/sh

pytest --pdb --pdbcls=debugpy:launcher
19 changes: 12 additions & 7 deletions scripts/oneWaySync.sh
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
#!/bin/sh
# Run habiticaTodo, avoid proxy.
unset http_proxy
unset https_proxy
#!/bin/bash
# This script should work, as long you keep in
# the same location in relation to the python code.
scriptdir=$(dirname "${BASH_SOURCE[0]}")
LOG_PATH=$XDG_STATE_HOME/oneWaySync/oneWaySync.log
exec 3>&1 4>&2
trap 'exec 2>&4 1>&3' 0 1 2 3
exec 1>>"$LOG_PATH" 2>&1

pwd
cd source
python3.9 one_way_sync.py
echo "Running at..."
date
cd "$scriptdir/../source"
python3 one_way_sync.py
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

setup(
name='Project_Hype-Berry',
version='2.1.0',
version='2.1.1',
url='https://github.com/programmerPhysicist/Project_Hype-Berry',
description='An API app for syncing todoist and habitica tasks',
packages=['Project_Hype-Berry'],
Expand Down
39 changes: 13 additions & 26 deletions source/hab_task.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@
unicode_literals)
from builtins import *
from datetime import datetime
from tzlocal import get_localzone
import copy
import time
# from tzlocal import get_localzone
import pytz

from dates import parse_date_utc
from task import CharacterAttribute, ChecklistItem, Difficulty, Task
from dateutil import parser


class HabTask():
Expand Down Expand Up @@ -61,8 +63,6 @@
@property
def due(self):
""" returns UTC due date """
from dateutil import parser
from datetime import datetime
if self.__task_dict['type'] == 'todo' and self.__task_dict['date'] != '':
date = parser.parse(self.__task_dict['date'])
return date
Expand All @@ -78,7 +78,7 @@
@property
def starting(self):
"""When did the daily start running? (That is, is it active now?)"""
from dateutil import parser

Check warning

Code scanning / Prospector (reported by Codacy)

Unable to import 'dateutil' (import-error) Warning

Unable to import 'dateutil' (import-error)
import datetime
if self.__task_dict['type'] == 'daily':
start = parser.parse(self.__task_dictself.__task_dict['startDate'])
Expand Down Expand Up @@ -166,17 +166,11 @@
else:
return "D"


@property
def name(self):
""" Task name """
return self.__task_dict['text']

@name.setter
def name(self, name):
""" Task name """
self.__task_dict['text'] = name

@property
def alias(self):
""" Task name """
Expand Down Expand Up @@ -216,32 +210,16 @@
""" Task type """
return self.__task_dict['type']

@category.setter
def category(self, name):
""" Task name """
self.__task_dict['type'] = name

@property
def description(self):
""" Task description """
return self.__task_dict['notes']

@description.setter
def description(self, description):
""" Task description """
self.__task_dict['notes'] = description

@property
def completed(self):
""" Task completed """
return self.__task_dict['completed']

# TODO: Doesn't work
@completed.setter
def completed(self, completed):
""" Task completed """
self.__task_dict['completed'] = completed

@property
def difficulty(self):
""" Task difficulty """
Expand All @@ -266,10 +244,10 @@
raise TypeError
self.__task_dict['attribute'] = attribute.value

'''
@property
def due_date(self):
""" The due date if there is one, or None. """
from dates import parse_date_utc
datestr = self.__task_dict.get('date', None)
if datestr:
return parse_date_utc(datestr, milliseconds=True)
Expand All @@ -286,6 +264,7 @@
due_date.astimezone(get_localzone()).date()
elif 'date' in self.__task_dict:
del self.__task_dict['date']
'''

@property
def last_modified(self):
Expand Down Expand Up @@ -324,3 +303,11 @@
self.new_checklist_items.append({
'text': i.name,
'completed': i.checked})

def get_dict(self):
""" Get string representation of hab_task class. """
result_dict = copy.deepcopy(self.__task_dict)
if result_dict['date'] is not None:
due = result_dict['date'].strftime("%m/%d/%Y, %H:%M:%S")
result_dict['date'] = due
return result_dict
17 changes: 12 additions & 5 deletions source/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

# TODO: Main.py overdue for an overhaul! Let's see.
# Version control, basic paths
VERSION = 'Project_Hype-Berry version 2.1.0'
VERSION = 'Project_Hype-Berry version 2.1.1'
TASK_VALUE_BASE = 0.9747 # http://habitica.wikia.com/wiki/Task_Value
HABITICA_REQUEST_WAIT_TIME = 0.5 # time to pause between concurrent requests
HABITICA_TASKS_PAGE = '/#/tasks'
Expand Down Expand Up @@ -312,13 +312,16 @@
def make_hab_from_tod(tod_task):
new_hab = {'type': 'todo'}
new_hab['text'] = tod_task.name
due = tod_task.due_date
'''
try:
date_listed = list(tod_task.task_dict['due'])
due_now = str(parser.parse(date_listed).date())
except:
due_now = ''
'''

new_hab['date'] = due_now
new_hab['date'] = due
new_hab['alias'] = tod_task.id
if tod_task.priority == 1:
new_hab['priority'] = '2'
Expand Down Expand Up @@ -463,16 +466,20 @@
habDict['priority'] = 1

try:
due_now = tod.due.date()
due_now = tod.due_date
except:
due_now = ''
try:
due_old = parse_date_utc(hab.date).date()
due_old = hab.due
except:
due_old = ''

if due_old != due_now:
habDict['date'] = str(due_now)
if due_now is not None:
habDict['date'] = str(due_now)
print("INFO: Due date will be updated to " + habDict['date'])
else:
habDict['date'] = ''

new_hab = HabTask(habDict)
return new_hab
Expand Down Expand Up @@ -604,7 +611,7 @@
print(response)
print('Updated hab %s !' % hab.name)

match_dict[tid]['hab'] = hab

Check notice

Code scanning / Pylint (reported by Codacy)

String statement has no effect Note

String statement has no effect
'''
for hab in aliasError:
for tid in match_dict:
Expand Down
24 changes: 20 additions & 4 deletions source/one_way_sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

'''
One way sync. All the features of todoist-habitrpg; nothing newer or shinier.
Well. Okay, not *technically* one-way--it will sync two way for simple tasks/
Well. Okay, not *technically* oneway - it will sync two way for simple tasks/
habitica to-dos,
just not for recurring todo tasks or dailies. I'm workin' on that.
'''
Expand All @@ -12,6 +12,9 @@
import pickle
import time
import json
import pytz
import requests
from tzlocal import get_localzone

import main
from todo_task import TodTask
Expand All @@ -29,6 +32,8 @@
tasks = api.get_tasks()
except ConnectionError as error:
print(error)
except requests.exceptions.HTTPError as error:
print(error)
return tasks, api


Expand Down Expand Up @@ -62,8 +67,18 @@
todoist_tasks, todo_api = get_tasks(todo_token) # todoist_tasks used to be tod_tasks

tod_tasks = []
for i in range(0, len(todoist_tasks)):
tod_tasks.append(TodTask(todoist_tasks[i]))
tzone = None
for task in todoist_tasks:
tod_tasks.append(TodTask(task))

if tzone is None:
# assumption is that timezone from Todoist
# is the same as local timezone
tzone = pytz.timezone(str(get_localzone()))

for task in tod_tasks:
if task.due != '':
task.due_date = task.due.astimezone(tzone)

# TODO: add back to filter out repeating older than a certain amount?
# date stuff
Expand Down Expand Up @@ -98,7 +113,7 @@
new_hab = main.make_daily_from_tod(tod)
else:
new_hab = main.make_hab_from_tod(tod)
new_dict = new_hab.task_dict
new_dict = new_hab.get_dict()

# sleep to stay within rate limits
time.sleep(2)
Expand Down Expand Up @@ -195,6 +210,7 @@
if not hab.completed:
matched_hab = main.sync_hab2todo(hab, tod)
response = main.update_hab(matched_hab)
# TODO: handle error if response bad
elif hab.completed:
# fix_tod = todo_api.items.get_by_id(tid)
# fix_tod.close()
Expand Down
10 changes: 0 additions & 10 deletions test/__init__.py

This file was deleted.

16 changes: 0 additions & 16 deletions test/conftest.py

This file was deleted.

Loading
Loading