2020-05-09 11:08:53 -04:00
#!/usr/bin/python3
2020-05-24 11:46:51 -04:00
from todoist . api import TodoistAPI
import sys
import time
import requests
import argparse
import logging
2020-05-29 16:11:27 -04:00
from datetime import datetime
2020-05-24 09:55:47 -04:00
global overview_item_ids
global overview_item_labels
2020-05-30 07:14:07 -04:00
def make_wide ( formatter , w = 120 , h = 36 ) :
""" Return a wider HelpFormatter, if possible. """
try :
# https://stackoverflow.com/a/5464440
# beware: "Only the name of this class is considered a public API."
kwargs = { ' width ' : w , ' max_help_position ' : h }
formatter ( None , * * kwargs )
return lambda prog : formatter ( prog , * * kwargs )
except TypeError :
2020-05-30 07:49:37 -04:00
logging . error ( " Argparse help formatter failed, falling back. " )
2020-05-30 07:14:07 -04:00
return formatter
2020-05-09 11:08:53 -04:00
def main ( ) :
2020-05-24 05:11:17 -04:00
# Version
2020-06-13 11:45:08 -04:00
current_version = ' v1.3 '
2020-05-24 05:11:17 -04:00
2020-05-09 11:08:53 -04:00
""" Main process function. """
2020-05-30 07:14:07 -04:00
parser = argparse . ArgumentParser (
formatter_class = make_wide ( argparse . HelpFormatter , w = 110 , h = 50 ) )
2020-06-07 08:59:47 -04:00
parser . add_argument ( ' -a ' , ' --api_key ' ,
help = ' Takes your Todoist API Key. ' , type = str )
2020-05-24 05:11:17 -04:00
parser . add_argument (
2020-05-30 07:14:07 -04:00
' -l ' , ' --label ' , help = ' Enable next action labelling. Define which label to use. ' , type = str )
2020-05-24 05:11:17 -04:00
parser . add_argument (
2020-05-30 07:14:07 -04:00
' -r ' , ' --recurring ' , help = ' Enable regeneration of sub-tasks in recurring lists. ' , action = ' store_true ' )
2020-05-24 05:11:17 -04:00
parser . add_argument (
2020-05-30 07:14:07 -04:00
' -e ' , ' --end ' , help = ' Enable alternative end-of-day time instead of default midnight. Enter a number from 1 to 24 to define which hour is used. ' , type = int )
2020-05-24 05:11:17 -04:00
parser . add_argument (
2020-05-30 07:14:07 -04:00
' -d ' , ' --delay ' , help = ' Specify the delay in seconds between syncs (default 5). ' , default = 5 , type = int )
2020-05-24 05:11:17 -04:00
parser . add_argument (
2020-06-13 05:13:03 -04:00
' -pp ' , ' --pp_suffix ' , help = ' Change suffix for parallel-parallel labeling (default " // " ). ' , default = ' // ' )
2020-05-29 16:11:27 -04:00
parser . add_argument (
2020-06-13 05:13:03 -04:00
' -ss ' , ' --ss_suffix ' , help = ' Change suffix for sequential-sequential labeling (default " -- " ). ' , default = ' -- ' )
parser . add_argument (
' -ps ' , ' --ps_suffix ' , help = ' Change suffix for parallel-sequential labeling (default " /- " ). ' , default = ' /- ' )
parser . add_argument (
' -sp ' , ' --sp_suffix ' , help = ' Change suffix for sequential-parallel labeling (default " -/ " ). ' , default = ' -/ ' )
2020-06-13 09:26:56 -04:00
parser . add_argument (
2020-06-13 11:45:08 -04:00
' -df ' , ' --dateformat ' , help = ' Strptime() format of starting date (default " %% d- %% m- %% Y " ). ' , default = ' %d - % m- % Y ' )
2020-06-13 09:26:56 -04:00
parser . add_argument (
2020-06-13 11:45:08 -04:00
' -hf ' , ' --hide_future ' , help = ' Prevent labelling of future tasks beyond a specified number of days. ' , default = 0 , type = int )
2020-05-30 07:14:07 -04:00
parser . add_argument (
' --onetime ' , help = ' Update Todoist once and exit. ' , action = ' store_true ' )
parser . add_argument (
' --nocache ' , help = ' Disables caching data to disk for quicker syncing. ' , action = ' store_true ' )
parser . add_argument ( ' --debug ' , help = ' Enable detailed debugging in log. ' ,
action = ' store_true ' )
2020-06-07 08:59:47 -04:00
parser . add_argument ( ' --inbox ' , help = ' The method the Inbox should be processed with. ' ,
2020-05-30 07:14:07 -04:00
default = None , choices = [ ' parallel ' , ' sequential ' ] )
2020-06-13 09:26:56 -04:00
2020-05-09 11:08:53 -04:00
args = parser . parse_args ( )
2020-05-24 11:46:51 -04:00
# Set debug
if args . debug :
log_level = logging . DEBUG
else :
log_level = logging . INFO
2020-05-09 16:03:43 -04:00
2020-05-24 11:46:51 -04:00
logging . basicConfig ( level = log_level ,
format = ' %(asctime)s %(levelname)-8s %(message)s ' ,
datefmt = ' % Y- % m- %d % H: % M: % S ' ,
handlers = [ logging . FileHandler (
' debug.log ' , ' w+ ' , ' utf-8 ' ) ,
logging . StreamHandler ( ) ]
)
2020-05-09 11:08:53 -04:00
2020-05-30 07:49:37 -04:00
def sync ( api ) :
try :
logging . debug ( ' Syncing the current state from the API ' )
api . sync ( )
except Exception as e :
logging . exception (
' Error trying to sync with Todoist API: %s ' % str ( e ) )
quit ( )
2020-06-07 08:59:47 -04:00
2020-06-07 10:20:55 -04:00
def query_yes_no ( question , default = " yes " ) :
2020-06-13 09:26:56 -04:00
# """Ask a yes/no question via raw_input() and return their answer.
2020-06-07 10:20:55 -04:00
2020-06-13 09:26:56 -04:00
# "question" is a string that is presented to the user.
# "default" is the presumed answer if the user just hits <Enter>.
# It must be "yes" (the default), "no" or None (meaning
# an answer is required of the user).
2020-06-07 10:20:55 -04:00
2020-06-13 09:26:56 -04:00
# The "answer" return value is True for "yes" or False for "no".
# """
2020-06-07 10:20:55 -04:00
valid = { " yes " : True , " y " : True , " ye " : True ,
" no " : False , " n " : False }
if default is None :
prompt = " [y/n] "
elif default == " yes " :
prompt = " [Y/n] "
elif default == " no " :
prompt = " [y/N] "
else :
raise ValueError ( " invalid default answer: ' %s ' " % default )
while True :
sys . stdout . write ( question + prompt )
choice = input ( ) . lower ( )
if default is not None and choice == ' ' :
return valid [ default ]
elif choice in valid :
return valid [ choice ]
else :
sys . stdout . write ( " Please respond with ' yes ' or ' no ' "
" (or ' y ' or ' n ' ). \n " )
2020-05-24 11:46:51 -04:00
def initialise ( args ) :
2020-05-30 07:14:07 -04:00
2020-05-09 11:08:53 -04:00
# Check we have a API key
if not args . api_key :
2020-05-30 07:14:07 -04:00
logging . error (
" \n \n No API key set. Run Autodoist with ' -a <YOUR_API_KEY> ' \n " )
2020-05-09 11:08:53 -04:00
sys . exit ( 1 )
2020-05-30 06:29:25 -04:00
# Check if AEOD is used
if args . end is not None :
if args . end < 1 or args . end > 24 :
2020-05-30 07:14:07 -04:00
logging . error (
" \n \n Please choose a number from 1 to 24 to indicate which hour is used as alternative end-of-day time. \n " )
2020-05-30 06:29:25 -04:00
sys . exit ( 1 )
else :
pass
2020-05-29 16:11:27 -04:00
2020-06-07 08:59:47 -04:00
# Show which modes are enabled:
modes = [ ]
m_num = 0
for x in [ args . label , args . recurring , args . end ] :
if x :
modes . append ( ' Enabled ' )
m_num + = 1
else :
modes . append ( ' Disabled ' )
2020-06-07 09:18:12 -04:00
logging . info ( " You are running with the following functionalities: \n \n Next action labelling mode: {} \n Regenerate sub-tasks mode: {} \n Shifted end-of-day mode: {} \n " . format ( * modes ) )
2020-06-07 08:59:47 -04:00
if m_num == 0 :
logging . info ( " \n No functionality has been enabled. Please see --help for the available options. \n " )
exit ( 0 )
2020-05-09 11:08:53 -04:00
# Run the initial sync
logging . debug ( ' Connecting to the Todoist API ' )
api_arguments = { ' token ' : args . api_key }
if args . nocache :
logging . debug ( ' Disabling local caching ' )
api_arguments [ ' cache ' ] = None
api = TodoistAPI ( * * api_arguments )
2020-05-30 07:49:37 -04:00
sync ( api )
2020-05-30 06:29:25 -04:00
# Check if label argument is used
if args . label is not None :
# Check the next action label exists
labels = api . labels . all ( lambda x : x [ ' name ' ] == args . label )
if len ( labels ) > 0 :
label_id = labels [ 0 ] [ ' id ' ]
logging . debug ( ' Label \' %s \' found as label id %d ' ,
2020-05-30 07:14:07 -04:00
args . label , label_id )
2020-05-30 06:29:25 -04:00
else :
# Create a new label in Todoist
2020-05-30 07:14:07 -04:00
#TODO:
2020-06-07 09:18:12 -04:00
logging . info (
2020-06-07 10:20:55 -04:00
" \n \n Label ' {} ' doesn ' t exist in your Todoist \n " . format ( args . label ) )
2020-06-07 09:18:12 -04:00
# sys.exit(1)
2020-06-07 10:20:55 -04:00
response = query_yes_no ( ' Do you want to automatically create this label? ' )
if response :
api . labels . add ( args . label )
api . commit ( )
api . sync ( )
labels = api . labels . all ( lambda x : x [ ' name ' ] == args . label )
label_id = labels [ 0 ] [ ' id ' ]
logging . info ( ' Label {} has been created! ' . format ( args . label ) )
else :
logging . info ( ' Exiting Autodoist. ' )
exit ( 1 )
2020-05-09 11:08:53 -04:00
else :
2020-05-30 06:29:25 -04:00
# Label functionality not needed
label_id = None
2020-05-24 05:11:17 -04:00
2020-06-07 09:18:12 -04:00
logging . info ( " Autodoist has connected and is running fine! \n " )
2020-05-24 11:46:51 -04:00
2020-05-09 11:08:53 -04:00
return api , label_id
2020-05-24 05:11:17 -04:00
def check_for_update ( current_version ) :
updateurl = ' https://api.github.com/repos/Hoffelhas/autodoist/releases '
try :
r = requests . get ( updateurl )
r . raise_for_status ( )
release_info_json = r . json ( )
if not current_version == release_info_json [ 0 ] [ ' tag_name ' ] :
2020-05-24 11:46:51 -04:00
logging . warning ( " \n \n Your version is not up-to-date! \n Your version: {} . Latest version: {} \n See latest version at: {} \n " . format (
2020-05-24 05:11:17 -04:00
current_version , release_info_json [ 0 ] [ ' tag_name ' ] , release_info_json [ 0 ] [ ' html_url ' ] ) )
return 1
else :
return 0
except requests . exceptions . ConnectionError as e :
logging . error (
" Error while checking for updates (Connection error): {} " . format ( e ) )
return 1
except requests . exceptions . HTTPError as e :
logging . error (
" Error while checking for updates (HTTP error): {} " . format ( e ) )
return 1
except requests . exceptions . RequestException as e :
logging . error ( " Error while checking for updates: {} " . format ( e ) )
return 1
2020-12-06 17:08:47 -05:00
def check_name ( name ) :
2020-06-13 05:13:03 -04:00
len_suffix = [ len ( args . pp_suffix ) , len ( args . ss_suffix ) , len ( args . ps_suffix ) , len ( args . sp_suffix ) ]
2020-05-09 11:08:53 -04:00
if name == ' Inbox ' :
current_type = args . inbox
2020-06-13 05:13:03 -04:00
elif name [ - len_suffix [ 0 ] : ] == args . pp_suffix :
2020-05-24 05:11:17 -04:00
current_type = ' parallel '
2020-06-13 05:13:03 -04:00
elif name [ - len_suffix [ 1 ] : ] == args . ss_suffix :
2020-05-17 12:02:38 -04:00
current_type = ' sequential '
2020-06-13 05:13:03 -04:00
elif name [ - len_suffix [ 1 ] : ] == args . ps_suffix :
current_type = ' p-s '
elif name [ - len_suffix [ 1 ] : ] == args . sp_suffix :
current_type = ' s-p '
2020-05-09 11:08:53 -04:00
else :
current_type = None
2020-12-06 17:08:47 -05:00
return current_type
def get_type ( object , key ) :
#TODO: API variable needs to be parsed to this function in order to make this work.
try :
old_type = object [ key ]
2020-12-07 11:44:12 -05:00
except :
2020-12-06 17:08:47 -05:00
# logging.debug('No defined project_type: %s' % str(e))
old_type = None
try :
object_name = object [ ' name ' ] . strip ( )
except :
object_name = object [ ' content ' ] . strip ( )
current_type = check_name ( object_name )
2020-05-09 11:08:53 -04:00
# Check if project type changed with respect to previous run
if old_type == current_type :
type_changed = 0
else :
type_changed = 1
object [ key ] = current_type
return current_type , type_changed
def get_project_type ( project_object ) :
""" Identifies how a project should be handled. """
2020-05-24 05:11:17 -04:00
project_type , project_type_changed = get_type (
project_object , ' project_type ' )
2020-05-09 11:08:53 -04:00
return project_type , project_type_changed
2020-12-07 11:44:12 -05:00
def get_section_type ( item_object , api ) :
""" Identifies how a section should be handled. """
if api is not None :
section = api . sections . all ( lambda x : x [ ' id ' ] == item_object [ ' section_id ' ] )
2020-12-07 16:45:12 -05:00
if section :
section_type , section_type_changed = get_type ( section [ 0 ] , ' section_type ' )
# Hier of later inbouwen?
# try:
# first_change = section['first_change']
# except:
# section['first_change'] = 0
else :
section_type = None
section_type_changed = 0
if section_type is not None :
print ( section [ 0 ] [ ' name ' ] )
print ( section_type )
2020-12-07 11:44:12 -05:00
else :
section_type = None
section_type_changed = 0
return section_type , section_type_changed
def get_item_type ( item , project_type , api ) :
2020-05-09 11:08:53 -04:00
""" Identifies how a item with sub items should be handled. """
2020-05-24 05:11:17 -04:00
2020-05-09 11:08:53 -04:00
if project_type is None and item [ ' parent_id ' ] != 0 :
try :
item_type = item [ ' parent_type ' ]
item_type_changed = 1
2020-05-24 09:55:47 -04:00
item [ ' item_type ' ] = item_type
2020-05-09 11:08:53 -04:00
except :
2020-05-24 05:11:17 -04:00
item_type , item_type_changed = get_type ( item , ' item_type ' )
2020-05-09 11:08:53 -04:00
else :
2020-05-24 05:11:17 -04:00
item_type , item_type_changed = get_type ( item , ' item_type ' )
2020-05-09 11:08:53 -04:00
2020-12-07 11:44:12 -05:00
# If no type is found in parentless task name, try to find one in the section name.
if item_type is None :
2020-12-07 16:45:12 -05:00
section_type , section_type_changed = get_section_type ( item , api )
2020-12-07 11:44:12 -05:00
else :
section_type = None
section_type_changed = 0
return item_type , item_type_changed , section_type , section_type_changed
2020-05-09 11:08:53 -04:00
def add_label ( item , label ) :
if label not in item [ ' labels ' ] :
labels = item [ ' labels ' ]
logging . debug ( ' Updating \' %s \' with label ' , item [ ' content ' ] )
labels . append ( label )
2020-05-24 11:46:51 -04:00
2020-05-24 09:55:47 -04:00
try :
overview_item_ids [ str ( item [ ' id ' ] ) ] + = 1
except :
overview_item_ids [ str ( item [ ' id ' ] ) ] = 1
overview_item_labels [ str ( item [ ' id ' ] ) ] = labels
2020-05-24 11:46:51 -04:00
2020-05-09 11:08:53 -04:00
def remove_label ( item , label ) :
if label in item [ ' labels ' ] :
labels = item [ ' labels ' ]
logging . debug ( ' Removing \' %s \' of its label ' , item [ ' content ' ] )
labels . remove ( label )
2020-05-24 09:55:47 -04:00
try :
overview_item_ids [ str ( item [ ' id ' ] ) ] - = 1
except :
overview_item_ids [ str ( item [ ' id ' ] ) ] = - 1
overview_item_labels [ str ( item [ ' id ' ] ) ] = labels
def update_labels ( label_id ) :
2020-05-24 11:46:51 -04:00
filtered_overview_ids = [
k for k , v in overview_item_ids . items ( ) if v != 0 ]
2020-05-24 09:55:47 -04:00
for item_id in filtered_overview_ids :
labels = overview_item_labels [ item_id ]
2020-05-24 11:46:51 -04:00
api . items . update ( item_id , labels = labels )
2020-05-24 05:11:17 -04:00
# Check for updates
check_for_update ( current_version )
2020-05-09 11:08:53 -04:00
# Initialise api
api , label_id = initialise ( args )
# Main loop
while True :
2020-05-24 09:55:47 -04:00
overview_item_ids = { }
overview_item_labels = { }
2020-05-30 07:49:37 -04:00
sync ( api )
2020-05-24 09:55:47 -04:00
for project in api . projects . all ( ) :
2020-05-30 07:14:07 -04:00
2020-05-30 06:29:25 -04:00
if label_id is not None :
# Get project type
project_type , project_type_changed = get_project_type ( project )
logging . debug ( ' Project \' %s \' being processed as %s ' ,
2020-05-30 07:14:07 -04:00
project [ ' name ' ] , project_type )
2020-05-24 09:55:47 -04:00
# Get all items for the project
items = api . items . all (
lambda x : x [ ' project_id ' ] == project [ ' id ' ] )
# Change top parents_id in order to sort later on
for item in items :
if not item [ ' parent_id ' ] :
item [ ' parent_id ' ] = 0
# Sort by parent_id and filter for completable items
items = sorted ( items , key = lambda x : (
x [ ' parent_id ' ] , x [ ' child_order ' ] ) )
items = list (
filter ( lambda x : not x [ ' content ' ] . startswith ( ' * ' ) , items ) )
2020-05-30 06:29:25 -04:00
if label_id is not None :
# If project type has been changed, clean everything for good measure
if project_type_changed == 1 :
# Remove labels
[ remove_label ( item , label_id ) for item in items ]
# Remove parent types
for item in items :
item [ ' parent_type ' ] = None
2020-05-24 09:55:47 -04:00
2020-05-30 06:29:25 -04:00
# To determine if a sequential task was found
first_found_project = False
2020-12-07 11:44:12 -05:00
first_found_section = False
2020-05-30 06:29:25 -04:00
first_found_item = True
2020-05-24 09:55:47 -04:00
# For all items in this project
for item in items :
# Determine which child_items exist, both all and the ones that have not been checked yet
non_checked_items = list (
filter ( lambda x : x [ ' checked ' ] == 0 , items ) )
child_items_all = list (
filter ( lambda x : x [ ' parent_id ' ] == item [ ' id ' ] , items ) )
child_items = list (
filter ( lambda x : x [ ' parent_id ' ] == item [ ' id ' ] , non_checked_items ) )
# Logic for recurring lists
if not args . recurring :
try :
# If old label is present, reset it
if item [ ' r_tag ' ] == 1 :
item [ ' r_tag ' ] = 0
api . items . update ( item [ ' id ' ] )
2020-12-07 11:44:12 -05:00
except :
2020-05-24 09:55:47 -04:00
pass
2020-05-30 06:29:25 -04:00
# If options turned on, start recurring lists logic
if args . recurring or args . end :
2020-05-24 09:55:47 -04:00
if item [ ' parent_id ' ] == 0 :
2020-05-09 11:08:53 -04:00
try :
2020-05-24 09:55:47 -04:00
if item [ ' due ' ] [ ' is_recurring ' ] :
try :
# Check if the T0 task date has changed
2020-05-30 06:29:25 -04:00
if item [ ' due ' ] [ ' date ' ] != item [ ' date_old ' ] :
2020-05-30 07:14:07 -04:00
2020-05-30 06:29:25 -04:00
if args . end is not None :
# Determine current hour
t = datetime . today ( )
current_hour = t . hour
2020-05-30 07:14:07 -04:00
2020-05-30 06:29:25 -04:00
# Check if current time is before our end-of-day
if ( args . end - current_hour ) > 0 :
2020-05-29 16:11:27 -04:00
2020-05-30 06:29:25 -04:00
# Determine the difference in days set by todoist
2020-05-30 07:14:07 -04:00
nd = [
int ( x ) for x in item [ ' due ' ] [ ' date ' ] . split ( ' - ' ) ]
od = [
int ( x ) for x in item [ ' date_old ' ] . split ( ' - ' ) ]
new_date = datetime (
nd [ 0 ] , nd [ 1 ] , nd [ 2 ] )
old_date = datetime (
od [ 0 ] , od [ 1 ] , od [ 2 ] )
today = datetime (
t . year , t . month , t . day )
days_difference = (
new_date - today ) . days
days_overdue = (
today - old_date ) . days
2020-05-30 06:29:25 -04:00
# Only apply if overdue and if it's a daily recurring tasks
if days_overdue > = 1 and days_difference == 1 :
# Find curreny date in string format
2020-05-30 07:14:07 -04:00
today_str = [ str ( x ) for x in [
today . year , today . month , today . day ] ]
2020-05-30 06:29:25 -04:00
if len ( today_str [ 1 ] ) == 1 :
2020-05-30 07:14:07 -04:00
today_str [ 1 ] = ' ' . join (
[ ' 0 ' , today_str [ 1 ] ] )
2020-05-30 06:29:25 -04:00
# Update due-date to today
item_due = item [ ' due ' ]
2020-05-30 07:14:07 -04:00
item_due [ ' date ' ] = ' - ' . join (
today_str )
2020-05-30 06:29:25 -04:00
item . update ( due = item_due )
# item.update(due={'date': '2020-05-29', 'is_recurring': True, 'string': 'every day'})
2020-05-29 16:11:27 -04:00
# Save the new date for reference us
2020-05-30 07:14:07 -04:00
item . update (
date_old = item [ ' due ' ] [ ' date ' ] )
2020-05-24 05:11:17 -04:00
2020-05-24 09:55:47 -04:00
# Mark children for action
2020-05-30 06:29:25 -04:00
if args . recurring is True :
for child_item in child_items_all :
child_item [ ' r_tag ' ] = 1
2020-05-28 15:51:20 -04:00
2020-12-07 11:44:12 -05:00
except :
2020-05-24 09:55:47 -04:00
# If date has never been saved before, create a new entry
logging . debug (
2020-05-30 06:29:25 -04:00
' New recurring task detected: %s ' % item [ ' content ' ] )
item [ ' date_old ' ] = item [ ' due ' ] [ ' date ' ]
2020-05-24 09:55:47 -04:00
api . items . update ( item [ ' id ' ] )
2020-05-24 05:11:17 -04:00
2020-12-07 11:44:12 -05:00
except :
2020-05-24 09:55:47 -04:00
logging . debug (
2020-05-30 06:29:25 -04:00
' Parent not recurring: %s ' % item [ ' content ' ] )
2020-05-24 09:55:47 -04:00
pass
2020-05-30 06:29:25 -04:00
if args . recurring is True and item [ ' parent_id ' ] != 0 :
2020-05-09 11:08:53 -04:00
try :
if item [ ' r_tag ' ] == 1 :
item . update ( checked = 0 )
item . update ( in_history = 0 )
item [ ' r_tag ' ] = 0
api . items . update ( item [ ' id ' ] )
2020-05-21 04:00:06 -04:00
for child_item in child_items_all :
2020-05-09 11:08:53 -04:00
child_item [ ' r_tag ' ] = 1
2020-12-07 11:44:12 -05:00
except :
2020-05-30 07:14:07 -04:00
logging . debug ( ' Child not recurring: %s ' %
item [ ' content ' ] )
2020-05-09 11:08:53 -04:00
pass
2020-05-30 07:14:07 -04:00
2020-05-30 06:29:25 -04:00
# If options turned on, start labelling logic
if label_id is not None :
# Skip processing an item if it has already been checked
if item [ ' checked ' ] == 1 :
continue
2020-05-30 07:14:07 -04:00
2020-05-30 06:29:25 -04:00
# Check item type
2020-12-07 11:44:12 -05:00
item_type , item_type_changed , section_type , section_type_changed = get_item_type (
item , project_type , api )
2020-05-30 06:29:25 -04:00
logging . debug ( ' Identified \' %s \' as %s type ' ,
2020-05-30 07:14:07 -04:00
item [ ' content ' ] , item_type )
2020-05-30 06:29:25 -04:00
2020-12-07 16:45:12 -05:00
# If there is no item of section type
2020-05-30 06:29:25 -04:00
if item_type is None :
2020-12-07 16:45:12 -05:00
# Handle parentless tasks
2020-05-30 06:29:25 -04:00
if item [ ' parent_id ' ] == 0 :
2020-05-09 11:08:53 -04:00
item_type = project_type
2020-12-07 16:45:12 -05:00
# Handle other tasks
2020-05-30 06:29:25 -04:00
else :
try :
if item [ ' parent_type ' ] is None :
item_type = project_type
else :
item_type = item [ ' parent_type ' ]
except :
item_type = project_type
2020-05-24 09:55:47 -04:00
else :
2020-05-30 06:29:25 -04:00
# Reset in case that parentless task is tagged, overrules project
first_found_item = False
2020-05-24 09:55:47 -04:00
2020-05-30 06:29:25 -04:00
# If it is a parentless task
if item [ ' parent_id ' ] == 0 :
2020-06-13 05:13:03 -04:00
if project_type == ' sequential ' or project_type == ' s-p ' :
2020-05-30 06:29:25 -04:00
if not first_found_project :
add_label ( item , label_id )
first_found_project = True
2020-06-13 05:13:03 -04:00
elif not first_found_item and not project_type == ' s-p ' :
2020-05-30 06:29:25 -04:00
add_label ( item , label_id )
first_found_item = True
2020-06-13 05:13:03 -04:00
elif project_type == ' parallel ' or project_type == ' p-s ' :
2020-05-30 06:29:25 -04:00
add_label ( item , label_id )
2020-06-13 05:13:03 -04:00
2020-05-30 06:29:25 -04:00
else :
# If no project-type has been defined
if item_type :
add_label ( item , label_id )
# If there are children
if len ( child_items ) > 0 :
# Check if item state has changed, if so clean children for good measure
if item_type_changed == 1 :
[ remove_label ( child_item , label_id )
for child_item in child_items ]
2020-12-07 16:45:12 -05:00
#TODO: How to add if SECTION is sequential?
2020-05-30 06:29:25 -04:00
# Process sequential tagged items (item_type can overrule project_type)
2020-06-13 05:13:03 -04:00
if item_type == ' sequential ' or item_type == ' p-s ' :
2020-05-30 06:29:25 -04:00
for child_item in child_items :
# Pass item_type down to the children
child_item [ ' parent_type ' ] = item_type
# Pass label down to the first child
if child_item [ ' checked ' ] == 0 and label_id in item [ ' labels ' ] :
add_label ( child_item , label_id )
remove_label ( item , label_id )
else :
# Clean for good measure
remove_label ( child_item , label_id )
# Process parallel tagged items or untagged parents
2020-06-13 05:13:03 -04:00
elif item_type == ' parallel ' or ( item_type == ' s-p ' and label_id in item [ ' labels ' ] ) :
2020-05-24 09:55:47 -04:00
remove_label ( item , label_id )
2020-05-30 06:29:25 -04:00
for child_item in child_items :
child_item [ ' parent_type ' ] = item_type
if child_item [ ' checked ' ] == 0 :
# child_first_found = True
add_label ( child_item , label_id )
2020-06-13 09:26:56 -04:00
# If item is too far in the future, remove the next_action tag and skip
try :
if args . hide_future > 0 and ' due ' in item . data and item [ ' due ' ] is not None :
due_date = datetime . strptime ( item [ ' due ' ] [ ' date ' ] , " % Y- % m- %d " )
future_diff = ( due_date - datetime . today ( ) ) . days
if future_diff > = args . hide_future :
remove_label ( item , label_id )
continue
except :
# Hide-future not set, skip
continue
# If start-date has not passed yet, remove label
try :
f = item [ ' content ' ] . find ( ' start= ' )
if f > - 1 :
f_end = item [ ' content ' ] [ f + 6 : ] . find ( ' ' )
if f_end > - 1 :
start_date = item [ ' content ' ] [ f + 6 : f + 6 + f_end ]
else :
start_date = item [ ' content ' ] [ f + 6 : ]
start_date = datetime . strptime ( start_date , args . dateformat )
future_diff = ( datetime . today ( ) - start_date ) . days
if future_diff < 0 :
2020-05-30 06:29:25 -04:00
remove_label ( item , label_id )
continue
2020-05-24 09:55:47 -04:00
2020-06-13 09:26:56 -04:00
except Exception as e :
logging . exception (
' Error start-date: %s ' % str ( e ) )
continue
2020-05-24 09:55:47 -04:00
# Commit the queue with changes
2020-05-30 06:29:25 -04:00
if label_id is not None :
update_labels ( label_id )
2020-05-24 09:55:47 -04:00
if len ( api . queue ) :
2020-05-24 11:46:51 -04:00
len_api_q = len ( api . queue )
2020-05-24 09:55:47 -04:00
api . commit ( )
2020-05-30 07:14:07 -04:00
if len_api_q == 1 :
2020-05-24 11:46:51 -04:00
logging . info (
' %d change committed to Todoist. ' , len_api_q )
else :
logging . info (
' %d changes committed to Todoist. ' , len_api_q )
2020-05-24 09:55:47 -04:00
else :
2020-05-24 11:46:51 -04:00
logging . info ( ' No changes in queue, skipping sync. ' )
2020-05-09 11:08:53 -04:00
# If onetime is set, exit after first execution.
if args . onetime :
break
logging . debug ( ' Sleeping for %d seconds ' , args . delay )
time . sleep ( args . delay )
2020-06-07 08:59:47 -04:00
2020-05-09 11:08:53 -04:00
if __name__ == ' __main__ ' :
main ( )