Skip to content

Commit a39fc0b

Browse files
Fix duedate sync (#9)
Fix #5
1 parent 847da0b commit a39fc0b

22 files changed

+6547
-7791
lines changed

.codacy.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
---
2+
exclude_paths:
3+
- "**/test/**"

.flake8

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[flake8]
2+
extend-ignore = F401, F811

README.md

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
# Habitica+Todoist
2-
3-
## __:warning: Alpha script!__
1+
# Project Hype-Berry
42

3+
### __:warning: Alpha script!__
4+
⚠️ Only one way syncing is working right now.
5+
##
56
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.
67

7-
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:
8+
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:
89

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

1415
## INSTALLATION
1516

16-
There are a number dependencies you'll need to install, and the commands to install them are as follows:
17+
### Linux Installation
18+
1. Install the python dependencies:
1719
```
1820
pip install todoist_api_python requests scriptabit tzlocal iso8601 python-dateutil
1921
```
20-
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).
22+
2. Get the source code [here](https://github.com/programmerPhysicist/Project_Hype-Berry/tags)
23+
3. You need to add your API tokens to the **Project_Hype-Berry/source/auth.cfg.example** file
24+
* To get the _Habitica API User ID_ and _API key_ goto <https://habitica.com/user/settings/api> while logged in
25+
* To get the _Todoist API token_ goto <https://todoist.com/prefs/integrations> while logged in.
26+
4. Rename the file to **Project_Hype-Berry/source/auth.cfg** (remove the '.example' at the end).
27+
5. Add the folder oneWaySync to $XDG_STATE_HOME: `mkdir $XDG_STATE_HOME/oneWaySync`
28+
6. Add the **oneWaySync.sh** script to Crontab
2129

2230
## TASK DIFFICULTY
2331

24-
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`
32+
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**
2533

2634
Todoist priority | Habitica difficulty
2735
---------------- | -------------------
@@ -30,12 +38,14 @@ p2 | Medium
3038
p3 | Easy
3139
p4 | Easy
3240

33-
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.
41+
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.
3442

3543
## USAGE
3644

3745
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).
3846

47+
Or you can try the provided shell script **oneWaySync.sh** under **Project_Hype-Berry/scripts/**
48+
3949
## Credit
4050

4151
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.

projectHypeBerry.egg-info/PKG-INFO

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Metadata-Version: 1.0
22
Name: Project_Hype-Berry
3-
Version: 2.1.0
3+
Version: 2.1.1
44
Summary: An API app for syncing todoist and habitica tasks
55
Home-page: https://github.com/programmerPhysicist/Project_Hype-Berry
66
Author: UNKNOWN

pytest.ini

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[pytest]
2+
pythonpath = source test/fixtures

scripts/debugTests.sh

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
#!/bin/sh
2+
3+
pytest --pdb --pdbcls=debugpy:launcher

scripts/oneWaySync.sh

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

6-
pwd
7-
cd source
8-
python3.9 one_way_sync.py
10+
echo "Running at..."
11+
date
12+
cd "$scriptdir/../source"
13+
python3 one_way_sync.py

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
setup(
66
name='Project_Hype-Berry',
7-
version='2.1.0',
7+
version='2.1.1',
88
url='https://github.com/programmerPhysicist/Project_Hype-Berry',
99
description='An API app for syncing todoist and habitica tasks',
1010
packages=['Project_Hype-Berry'],

source/hab_task.py

Lines changed: 13 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,14 @@
1010
unicode_literals)
1111
from builtins import *
1212
from datetime import datetime
13-
from tzlocal import get_localzone
13+
import copy
1414
import time
15+
# from tzlocal import get_localzone
1516
import pytz
1617

1718
from dates import parse_date_utc
1819
from task import CharacterAttribute, ChecklistItem, Difficulty, Task
20+
from dateutil import parser
1921

2022

2123
class HabTask():
@@ -61,8 +63,6 @@ def task_dict(self):
6163
@property
6264
def due(self):
6365
""" returns UTC due date """
64-
from dateutil import parser
65-
from datetime import datetime
6666
if self.__task_dict['type'] == 'todo' and self.__task_dict['date'] != '':
6767
date = parser.parse(self.__task_dict['date'])
6868
return date
@@ -166,17 +166,11 @@ def hardness(self):
166166
else:
167167
return "D"
168168

169-
170169
@property
171170
def name(self):
172171
""" Task name """
173172
return self.__task_dict['text']
174173

175-
@name.setter
176-
def name(self, name):
177-
""" Task name """
178-
self.__task_dict['text'] = name
179-
180174
@property
181175
def alias(self):
182176
""" Task name """
@@ -216,32 +210,16 @@ def category(self):
216210
""" Task type """
217211
return self.__task_dict['type']
218212

219-
@category.setter
220-
def category(self, name):
221-
""" Task name """
222-
self.__task_dict['type'] = name
223-
224213
@property
225214
def description(self):
226215
""" Task description """
227216
return self.__task_dict['notes']
228217

229-
@description.setter
230-
def description(self, description):
231-
""" Task description """
232-
self.__task_dict['notes'] = description
233-
234218
@property
235219
def completed(self):
236220
""" Task completed """
237221
return self.__task_dict['completed']
238222

239-
# TODO: Doesn't work
240-
@completed.setter
241-
def completed(self, completed):
242-
""" Task completed """
243-
self.__task_dict['completed'] = completed
244-
245223
@property
246224
def difficulty(self):
247225
""" Task difficulty """
@@ -266,10 +244,10 @@ def attribute(self, attribute):
266244
raise TypeError
267245
self.__task_dict['attribute'] = attribute.value
268246

247+
'''
269248
@property
270249
def due_date(self):
271250
""" The due date if there is one, or None. """
272-
from dates import parse_date_utc
273251
datestr = self.__task_dict.get('date', None)
274252
if datestr:
275253
return parse_date_utc(datestr, milliseconds=True)
@@ -286,6 +264,7 @@ def due_date(self, due_date):
286264
due_date.astimezone(get_localzone()).date()
287265
elif 'date' in self.__task_dict:
288266
del self.__task_dict['date']
267+
'''
289268

290269
@property
291270
def last_modified(self):
@@ -324,3 +303,11 @@ def checklist(self, checklist):
324303
self.new_checklist_items.append({
325304
'text': i.name,
326305
'completed': i.checked})
306+
307+
def get_dict(self):
308+
""" Get string representation of hab_task class. """
309+
result_dict = copy.deepcopy(self.__task_dict)
310+
if result_dict['date'] is not None:
311+
due = result_dict['date'].strftime("%m/%d/%Y, %H:%M:%S")
312+
result_dict['date'] = due
313+
return result_dict

source/main.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
# TODO: Main.py overdue for an overhaul! Let's see.
1818
# Version control, basic paths
19-
VERSION = 'Project_Hype-Berry version 2.1.0'
19+
VERSION = 'Project_Hype-Berry version 2.1.1'
2020
TASK_VALUE_BASE = 0.9747 # http://habitica.wikia.com/wiki/Task_Value
2121
HABITICA_REQUEST_WAIT_TIME = 0.5 # time to pause between concurrent requests
2222
HABITICA_TASKS_PAGE = '/#/tasks'
@@ -312,13 +312,16 @@ def make_daily_from_tod(tod):
312312
def make_hab_from_tod(tod_task):
313313
new_hab = {'type': 'todo'}
314314
new_hab['text'] = tod_task.name
315+
due = tod_task.due_date
316+
'''
315317
try:
316318
date_listed = list(tod_task.task_dict['due'])
317319
due_now = str(parser.parse(date_listed).date())
318320
except:
319321
due_now = ''
322+
'''
320323

321-
new_hab['date'] = due_now
324+
new_hab['date'] = due
322325
new_hab['alias'] = tod_task.id
323326
if tod_task.priority == 1:
324327
new_hab['priority'] = '2'
@@ -463,16 +466,20 @@ def sync_hab2todo_todo(hab, tod):
463466
habDict['priority'] = 1
464467

465468
try:
466-
due_now = tod.due.date()
469+
due_now = tod.due_date
467470
except:
468471
due_now = ''
469472
try:
470-
due_old = parse_date_utc(hab.date).date()
473+
due_old = hab.due
471474
except:
472475
due_old = ''
473476

474477
if due_old != due_now:
475-
habDict['date'] = str(due_now)
478+
if due_now is not None:
479+
habDict['date'] = str(due_now)
480+
print("INFO: Due date will be updated to " + habDict['date'])
481+
else:
482+
habDict['date'] = ''
476483

477484
new_hab = HabTask(habDict)
478485
return new_hab

source/one_way_sync.py

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

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

1619
import main
1720
from todo_task import TodTask
@@ -29,6 +32,8 @@ def get_tasks(token):
2932
tasks = api.get_tasks()
3033
except ConnectionError as error:
3134
print(error)
35+
except requests.exceptions.HTTPError as error:
36+
print(error)
3237
return tasks, api
3338

3439

@@ -62,8 +67,18 @@ def sync_todoist_to_habitica():
6267
todoist_tasks, todo_api = get_tasks(todo_token) # todoist_tasks used to be tod_tasks
6368

6469
tod_tasks = []
65-
for i in range(0, len(todoist_tasks)):
66-
tod_tasks.append(TodTask(todoist_tasks[i]))
70+
tzone = None
71+
for task in todoist_tasks:
72+
tod_tasks.append(TodTask(task))
73+
74+
if tzone is None:
75+
# assumption is that timezone from Todoist
76+
# is the same as local timezone
77+
tzone = pytz.timezone(str(get_localzone()))
78+
79+
for task in tod_tasks:
80+
if task.due != '':
81+
task.due_date = task.due.astimezone(tzone)
6782

6883
# TODO: add back to filter out repeating older than a certain amount?
6984
# date stuff
@@ -98,7 +113,7 @@ def sync_todoist_to_habitica():
98113
new_hab = main.make_daily_from_tod(tod)
99114
else:
100115
new_hab = main.make_hab_from_tod(tod)
101-
new_dict = new_hab.task_dict
116+
new_dict = new_hab.get_dict()
102117

103118
# sleep to stay within rate limits
104119
time.sleep(2)
@@ -195,6 +210,7 @@ def sync_todoist_to_habitica():
195210
if not hab.completed:
196211
matched_hab = main.sync_hab2todo(hab, tod)
197212
response = main.update_hab(matched_hab)
213+
# TODO: handle error if response bad
198214
elif hab.completed:
199215
# fix_tod = todo_api.items.get_by_id(tid)
200216
# fix_tod.close()

test/__init__.py

Lines changed: 0 additions & 10 deletions
This file was deleted.

test/conftest.py

Lines changed: 0 additions & 16 deletions
This file was deleted.

0 commit comments

Comments
 (0)