Exemplo n.º 1
0
def main():
    print("Loading config...")
    with open('/home/pi/todoist-ordering/config.json', 'r') as f:
        config = json.load(f)
    api = TodoistAPI(config['apikey'])
    api.sync()
    projects_to_sort = config['projects']
    print("Sorting projects")
    for project in api.state['projects']:
        name = project['name']
        if name in projects_to_sort.keys():
            sortkeys = projects_to_sort[name]
            result = getTasksInProject(project['id'], api.state['items'])
            #     for item in result:
            #         print("Item Content: (" + item['content'] + ")")
            for sortkey in sortkeys:
                sortedResult = sortTasks(result, sortkey)
                print("Sorting project: '" + project['name'] + "' by key: '" +
                      sortkey + "'")
            thisOrder = 0
            for item in sortedResult:
                api.items.reorder([{
                    'id': item['id'],
                    'child_order': thisOrder
                }])
                thisOrder += 1
        #     print("After sort...")
        #     for item in result:
        #         print("Item Content: (" + item['content'] + ")")
            print("Committing...")
        else:
            print("No config for project: '" + project['name'] + "'")

    api.commit()
    print("DONE!")
Exemplo n.º 2
0
    def test_CreateTask(self):
        # read api token values
        apitoken = config.get('todoistinfo', 'apitoken')

        # read task name
        taskname = config.get('todoistinfo', 'taskname')

        # read created project ID
        projectid = config.get('todoistinfo', 'projectid')
        '''
        Create task by using API commands
        '''
        api = TodoistAPI(apitoken)
        api.sync()

        create_task = api.items.add(taskname, projectid)
        response = api.commit()

        # Verify if the task has been created successfully
        assert create_task[
            'content'] == taskname, "Task created unsuccessfully."
        assert taskname in [i['content'] for i in api.state['items']]
        assert api.items.get_by_id(create_task['id']) == create_task

        # update the created task ID to ini file
        config.set("todoistinfo", "taskid", str(create_task['id']))
        config.write(open('../test_info.ini', 'w'))
Exemplo n.º 3
0
def main(event, context):
    body = { "tasksUpdated": 0 }

    API_TOKEN = get_token()
    if not API_TOKEN:
        logging.warn('Please set the API token in environment variable.')
        exit()

    today = datetime.utcnow().replace(tzinfo=None)
    api = TodoistAPI(token=API_TOKEN, cache="/tmp/todoist")
    api.sync()
    tasks = api.state['items']
    for task in tasks:
        if task['due_date_utc'] and is_habit(task['content']):
            if is_today(task['due_date_utc']):
                habit = is_habit(task['content'])
                streak = int(habit.group(1)) + 1
                update_streak(task, streak)
                body["tasksUpdated"] += 1
            elif is_due(task['due_date_utc']):
                update_streak(task, 0)
                task.update(date_string='ev day starting tod')
    api.commit()


    response = {
        "statusCode": 200,
        "body": json.dumps(body)
    }

    return response
Exemplo n.º 4
0
def main():
    API_TOKEN = get_token()
    today = datetime.utcnow().replace(tzinfo=None)
    if not API_TOKEN:
        logging.error('Please set the API token in environment variable.')
        exit()
    api = TodoistAPI(API_TOKEN)
    api.sync()
    project_id = get_project(api)
    tasks = api.state['items']
    for task in tasks:
        content = task['content']
        if all([
            task['due_date_utc'],
            is_habit(content),
            not project_id or task['project_id'] == project_id
        ]):
            logger.info("Found task id:%s content:%s", task['id'], content[:20])
            date_string = task['date_string'] or 'ev day'
            task_id = task['id']
            due_at = parse(task['due_date_utc'], ignoretz=True)
            days_left = due_at.date() - today.date()
            if days_left:
                habit = is_habit(content)
                streak = int(habit.group(1)) + 1
                update_streak(task, streak)
                api.notes.add(task_id, '[BOT] Streak extended. Yay!')
            else:
                update_streak(task, 0)
                task.update(date_string=date_string + ' starting tod')
                api.notes.add(task_id, '[BOT] Chain broken :(')
    api.commit()
Exemplo n.º 5
0
    def test_CreateTask(self):
        # read api token values
        apitoken = config.get('todoistinfo', 'apitoken')

        # read created task name
        taskname = config.get('todoistinfo', 'taskname')

        # read created task ID
        taskid = config.get('todoistinfo', 'taskid')

        # Complete Task
        api = TodoistAPI(apitoken)
        api.sync()
        created_task = api.items.get_by_id(int(taskid))
        created_task.complete()
        response = api.commit()

        # Verify if the task has been completed successfully
        assert response['items'][0]['content'] == taskname
        assert response['items'][0]['checked'] == 1
        assert 1 in [
            i['checked'] for i in api.state['items'] if i['id'] == int(taskid)
        ]

        # Reopen Task
        created_task.uncomplete()
        response = api.commit()

        # Verify if the task has been reopened successfully
        assert response['items'][0]['content'] == taskname
        assert response['items'][0]['checked'] == 0
        assert 0 in [
            i['checked'] for i in api.state['items'] if i['id'] == int(taskid)
        ]
Exemplo n.º 6
0
class TodoistActionAPI(RequiredConfig):
    """An encapsulation of the Todoist API.  It wraps the lower level
    methods with methods appropriate for this task."""
    required_config = Namespace()
    required_config.add_option(
        name="api_key",
        default='',
        doc="Todoist API Key",
    )
    required_config.add_option(
        name="delay",
        default=5,
        doc="Specify the delay in seconds between syncs"
    )
    required_config.add_option(
        name="debug",
        default=False,
        doc="Enable debugging"
    )
    required_config.add_option(
        name="one_time",
        default=False,
        doc="Update Todoist once and exit"
    )
    def __init__(self, config, logging):
        super(TodoistActionAPI, self).__init__()
        self.config = config
        self.api = TodoistAPI(token=config.api_key)
        self.api.sync()
        self.logging = logging
Exemplo n.º 7
0
    def get_all_projects(self):

        api = TodoistAPI(self.apikey)

        api.sync()

        print(api.state["projects"])
Exemplo n.º 8
0
def main(subtasks_path, task_type, work_type):
    with console.status('[bold green]Initial Todoist API sync...') as _:
        api_token = os.getenv('TODOIST_API_TOKEN')
        api = TodoistAPI(api_token)
        api.sync()
    (project_id, _) = ui_select_project(api, work_type)
    branch_ref = ''
    repo = ''
    if task_type != DevTaskType.NON_MERGE:
        branch_ref = ui_get_jira_or_branch_ref()
    if task_type != DevTaskType.NON_MERGE:
        (_, repo, _) = ui_select_repository('Select Repository for Work')
    root_task_id = ui_create_root_dev_task(api, project_id, branch_ref,
                                           task_type, work_type, repo)
    if subtasks_path:
        ui_create_subtasks_from_file(api, subtasks_path, root_task_id,
                                     project_id, task_type, work_type)
    else:
        create_subtasks = questionary.confirm(
            'Would you like to create any subtasks').ask()
        if create_subtasks:
            ui_create_subtasks(api, root_task_id, project_id, task_type,
                               work_type)
    if task_type != DevTaskType.NON_MERGE:
        create_pr_checklist_subtask(api, project_id, root_task_id, branch_ref,
                                    task_type, work_type)
        create_merge_subtasks(api, project_id, root_task_id, branch_ref, repo,
                              'main', task_type, work_type)
    if os.getenv('JIRA_BASE_URL'):
        create_jira_admin_task(api, project_id, root_task_id, branch_ref)
Exemplo n.º 9
0
def find_task_by_name(content):
    api = TodoistAPI(TODOIST_API_TOKEN)
    api.sync()
    items = api.state['items']
    for item in items:
        if item['content'] == content:
            return (item['id'])
Exemplo n.º 10
0
def get_data():
    api = TodoistAPI(get_api_token())
    api.sync()

    tasks = api.items.all()

    tasks_with_labels = [t for t in tasks if len(t['labels']) != 0]
    tasks_without_labels = [t for t in tasks if len(t['labels']) == 0]

    test_data_size = math.floor(len(tasks_with_labels) * 0.8)
    test_data = tasks_with_labels[test_data_size:]
    training_data = tasks_with_labels[:test_data_size]

    save(test_data, 'test_data.p')
    save(training_data, 'training_data.p')
    save(tasks_without_labels, 'unlabeled_data.p')

    if debug():
        print('Num Tasks with no labels %s' % len(tasks_without_labels))
        print('Num Tasks with labels %s' % len(tasks_with_labels))

        print('Size of Training Data: %s' % len(training_data))
        print('Size of Test Data: %s' % len(test_data))

    return {
        'test_data': test_data,
        'training_data': training_data,
        'unlabeled_data': tasks_without_labels
    }
Exemplo n.º 11
0
    def test_CreateProject(self):
        #read api token values
        apitoken = config.get('todoistinfo', 'apitoken')

        # read project name
        projectname = config.get('todoistinfo', 'projectname')

        '''
        Create project by using API commands
        '''
        api = TodoistAPI(apitoken)
        api.sync()

        create_project = api.projects.add(projectname)
        response = api.commit()

        # Verify if the project has been created successfully
        assert response['projects'][0]['name'] == projectname
        assert projectname in [p['name'] for p in api.state['projects']]
        assert api.projects.get_by_id(create_project['id']) == create_project

        # update the created project ID to ini file
        config.set("todoistinfo", "projectid", str(create_project['id']))
        config.write(open('../test_info.ini', 'w'))

        #Login to mobile apps and verify the project has been created successfully
        Todoist.Login(self)
        sleep(6)
        Todoist.Verify_CreatedProject(self, projectname)
Exemplo n.º 12
0
class Todoist(Todolist):
    def __init__(self, list_name=None):
        print("Initializing Todoist API...")
        self.api = TodoistAPI(settings.todoist_key)  # TODO: other users
        self.api.sync()
        self.Project_ID = settings.todoist_project_key# TODO: add option for other projects
        self.project_name = list_name

        '''if list_name is not None:
            projects = self.api.projects
            for project in projects.all('name = ' + list_name):
                if project['name'] == list_name:
                    self.Project_ID = project['id']'''

    def add_task(self, name, desc=None, labels=None, due=None, assign=None):
        
        if labels is not None:
            for label in labels:
                name = name + ' @' + label
        if self.project_name is not None:
            name = name + ' #' + self.project_name
        if due is None:
            task = self.api.items.add(name, self.Project_ID)
        else:
            due = due - timedelta(hours=4)
            task = self.api.items.add(name, self.Project_ID, due_date_utc=due)

        task_id = task['id']

        # PREMIUM!
        #if desc is not None:
        #    self.api.notes.add(task_id, desc)

        print("\tAdded task: Name - " + name)
        self.api.commit()
Exemplo n.º 13
0
    def APIcall(caller):
        cur = datetime.now()

        if caller == 'Cal':
            return requests.get('https://calendarific.com/api/v2/holidays',\
                                params={'api_key':keys.CAL_API, 'country':'SG', 'year':str(cur.year)}\
                                ).json()

        elif caller == 'News':
            return requests.get('http://newsapi.org/v2/top-headlines',\
                                params={'country':'sg', 'apiKey':keys.NEWS_API}\
                                ).json()

        elif caller == 'Todo':
            todolist = []
            api = TodoistAPI(keys.TODO_API)
            api.sync()
            for i in range(len(api.state['items'])):
                temp = api.state['items'][i]
                if temp['checked'] == 0:
                    todolist.append([temp['content'], temp['due']['date']])
            return todolist

        elif caller == 'Weather':
            return requests.get('https://api.openweathermap.org/data/2.5/onecall',\
                                params={'lat':'1.370', 'lon':'103.955', 'exclude':'minutely,daily,current',\
                                'appid':keys.WEA_API, 'units':'metric'}\
                                ).json()
Exemplo n.º 14
0
def main():
    with console.status('[bold green]Initial Todoist API sync...') as _:
        api_token = os.getenv('TODOIST_API_TOKEN')
        api = TodoistAPI(api_token)
        api.sync()
    dev_tasks = []
    with console.status(
            '[bold green]Getting outstanding development tasks...') as _:
        dev_tasks = get_outstanding_dev_tasks(api, DevWorkType.WORK)
    task = select_dev_task(dev_tasks)
    if len(task.subtasks) > 3:
        console.print(
            '[bold red]Error:[/bold red] A task is only eligible for PR ' \
            'if all subtasks (except the PR/merge tasks) are cleared.'
        )
        console.print(
            '[bold green]Suggestion:[/bold green] Clear all the non-merge subtasks first.'
        )
        return 1
    pr_checklist = task.subtasks[0]
    process_pr_checklist(api, console, pr_checklist.subtasks)
    url = create_pr()
    with console.status(
            "[bold green]Marking development task as 'waiting'...") as _:
        task.mark_waiting(
            api,
            f'PR submitted, now waiting for review. PR is available at: {url}')
    submit_pr_task = task.subtasks[1]
    with console.status('[bold green]Completing submit PR task...') as _:
        submit_pr_task.complete(api)
Exemplo n.º 15
0
def main():
    api_token = os.getenv("TODOIST_API_TOKEN")
    with console.status("[bold green]Initial Todoist API sync...") as _:
        api = TodoistAPI(api_token)
        api.sync()
    link = questionary.text("Link?").ask()
    item_type = questionary.select("Type?", choices=["book", "film"]).ask()
    if item_type == "book":
        author = questionary.text("Author?").ask()
        title = questionary.text("Title?").ask()
        notes = questionary.text("Notes?").ask()
        create_item_to_buy(api, f"{author} - '{title}'", ["buy", "books"],
                           link, notes)
    elif item_type == "film":
        title = questionary.text("Title?").ask()
        media_type = questionary.select("Type?",
                                        choices=["Blu-ray", "Digital",
                                                 "DVD"]).ask()
        if media_type == "Blu-ray":
            sub_type = questionary.select("Blu-ray type?",
                                          choices=["1080p", "4k"]).ask()
            media_type += f" {sub_type}"
        notes = questionary.text("Notes?").ask()
        create_item_to_buy(api, f"'{title}' ({media_type})", ["buy", "films"],
                           link, notes)
    return 0
Exemplo n.º 16
0
class TodoistAccess:
    def __init__(
        self,
        path=os.path.join(
            os.path.dirname(__file__), "..", "resources", "secrets",
            "todoist_token.txt")):  #'../resources/secrets/todoist_token.txt'):
        with open(path, 'rb') as f:
            my_token = f.readline().decode("utf-8")
        self.api = TodoistAPI(my_token)
        self.api.sync()

    def get_all_tasks(self):
        self.sync()
        tasks = self.api.items.all()
        return tasks

    def sync(self):
        self.api.sync()

    def get_content(self, note):
        return note["content"]

    def get_all_content(self):
        tasks = self.get_all_tasks()
        return [self.get_content(task) for task in tasks]

    def get_tasks_info(self):
        tasks = self.get_all_tasks()
        return [(task["content"], task["due"]) for task in tasks]
Exemplo n.º 17
0
def pullSources(keys):
    canvasAPI = keys['Canvas']
    todoistAPI = keys['Todoist']
    courseList = []
    projectList = {}

    header = {"Authorization": "Bearer " + canvasAPI}
    parameter = {'per_page': 9999, 'include': 'submission'}
    for ID, course in keys['Courses'].items():
        try:
            assignments = requests.get("https://canvas.instructure.com/api/v1/courses/" + ID+"/assignments", headers=header, params=parameter).json()
            newCourse = Class(course[0], str(ID), str(course[1]))
            for assignment in assignments:
                if not assignment['submission']['submitted_at']:
                    newCourse.assignments.append([assignment['name'], assignment['due_at']])
                    
            courseList.append(newCourse)
        except:
            print(f"Error requesting {course[0]} from Canvas API")
        
        
    api = TodoistAPI(todoistAPI)
    api.sync()
    projectList = createProjects(courseList, api)
    api.sync()
    createTasks(api, projectList)
Exemplo n.º 18
0
def instantiate(project_name, loglevel, date_str):
    api = TodoistAPI(get_api_token())
    api.sync()

    logging.basicConfig(level=getattr(logging, loglevel.upper()))

    project_id = get_project_id(api, project_name)

    date = None if date_str is None else dateutil.parser.parse(date_str).date()
    logging.debug(f"date = {date!r}")

    tasks = api.projects.get_data(project_id)['items']
    instantiable_tasks = [
        task for task in tasks if is_instantiable(task, date)
    ]

    action_count = 0

    for task in instantiable_tasks:
        clone_task(api, task)
        postpone_task(api, task)
        action_count += 2

        # The Todoist API will reject a commit if there are more than 100
        # changes bundled together.  Just to give myself some buffer in case
        # I'm miscounting actions, I commit everytime the action count gets
        # above 80.
        if action_count > 80:
            r = api.commit()
            logging.debug(f"commit = {r!r}")
            action_count = 0

    r = api.commit()
    logging.debug(f"commit = {r!r}")
Exemplo n.º 19
0
def connect(parentdebuglog):
    if not args.api_key:
        logging.error('No API key set, exiting...')
        sys.exit(1)

    # Run the initial sync
    debuglog = parentdebuglog.sublogger('Connecting to the Todoist API')
    api = TodoistAPI(token=args.api_key)
    debuglog.log('Syncing the current state from the API')
    api.sync()

    # Check the NoDate label exists
    labels = api.labels.all(lambda x: x['name'] == args.label)
    if len(labels) > 0:
        global nodate_label_id
        nodate_label_id = labels[0]['id']
        debuglog.log('Label %s found as label id %d' % (args.label, nodate_label_id))
    else:
        debuglog.error("Label %s doesn't exist, please create it." % args.label)
        sys.exit(1)
    global next_label_ids
    next_label_ids = set(map(
        lambda x: x['id'],
        api.labels.all(lambda x: x['name'].startswith(args.next_prefix))
    ))
    debuglog.log('"Next" label ids: %s' % (next_label_ids))
    
    return api
Exemplo n.º 20
0
def handle_payload(request):
    if request.method == 'POST':
        received_json_data = json.loads(
            request.body)  # convert json data to python dict
        if 'events' in received_json_data.keys():
            events = received_json_data["events"]

            for event in events:
                id = event["resource"]
                type = event["type"]
                source = None  # by defualt make  url-source none
                if type == "task":
                    source = "https://app.asana.com/api/1.0/tasks/" + str(id)

                if not source == None:
                    data = requests.get(url=source,
                                        headers={"Authorisation": "Bearer "})

                    jsdata = json.loads(data)

                    if jsdata['data']['assignee']['name'] == 'Stebin Ben':
                        task_name = jsdata['data']['name']
                        api = TodoistAPI(
                            '7441fc19459367f6d69efaa79175461b521e257d')
                        api.sync()
                        project_name = jsdata['data']['memberships'][
                            'project']['name']
                        project1 = api.projects.add(project_name)
                        api.items.add(task_name, project1['id'])
                        api.commit()

        else:
            headers = request.headers
            x_hook = headers['X-Hook-Secret']
            return JsonResponse({"X-Hook-Sercret": x_hook})
Exemplo n.º 21
0
def todoist_task_uploader(cal, fin):
    """    
    Method to upload/create tasks with Todoist API

    Args:
        cal (string): [name of the project or calendar]
        fin (string): [path to the file]
    """
    school_pid = ''
    subject_count = 0
    task_count = 0
    # Connect with Todoist API
    api = TodoistAPI(os.getenv('API_TOKEN'))
    logging.info('Connection established!')
    api.sync()

    # Search for desire Project (SCHOOL)
    for project in api.state['projects']:
        if 'SCHOOL' in project['name']:
            school_pid = project['id']

    # read file
    logging.info('Reading file...')
    wb = load_workbook(fin)
    logging.info('Creating new project...')
    # Create new Semester Project
    semester = api.projects.add('Semester ' + cal, parent_id=school_pid, color=randint(30, 49))

    for materia in wb.worksheets:
        # Create Section per Subject(Worksheet)
        subject = materia.title.split('-')  # worksheet in format: Subject-label
        section = api.sections.add(subject[0], semester['id'])
        new_label = api.labels.add(subject[1])
        label_id = 2155146353 if subject[1] == 'proyecto' else new_label['id']
        subject_count += 1
        logging.info('Creating section: ' + subject[0])
        logging.info('Creating tasks...')
        for task in materia.values:
            # Create Tasks Per Section
            api.items.add(task[0],
                          due = {
                              "string": task[1],
                              "date": task[1],  # datetime must be text in excel with format: 2021-08-10 not 2021-8-10
                              "recurring": False,
                              },
                          section_id = section['id'],
                          description = task[2],  # type of activity: foro or buzon
                          labels = [2155113156, label_id])  # label 'INICIO' and section label
            task_count += 1

    # Commit Changes and Sync
    logging.info('Committing changes to API...')
    api.commit()
    logging.info("Sync'ing changes to API...")
    api.sync()

    # Print successful message
    logging.info('Successfully created {} section(s) with {} task(s).'.format(subject_count, task_count))
    logging.info('Work done!')
Exemplo n.º 22
0
def main():
    API_TOKEN = get_token()
    if not API_TOKEN:
        logging.warn('Please set the API token in environment variable.')
        exit()
    api = TodoistAPI(API_TOKEN)
    api.sync()
    return api
Exemplo n.º 23
0
def add_data(api_key, issues):
    """ add tasks to Todoist INBOX due today """
    api = TodoistAPI(api_key)
    api.sync()
    for v in issues:
        api.items.add(v, due={"string": "today"})

    api.commit()
Exemplo n.º 24
0
def initiate_api():
    API_TOKEN = get_token()
    if not API_TOKEN:
        logging.warning('Please set the API token in environment variable.')
        exit()
    api = TodoistAPI(API_TOKEN)
    api.sync()
    return api
Exemplo n.º 25
0
def show_projects(key=tovakey):
    api = TodoistAPI(key)
    api.sync()
    projects = []
    for project in api.state['projects']:
        projects.append(project['name'])
    projects_to_show = ', '.join([str(elem) for elem in projects])
    return query_response(value=projects_to_show, grammar_entry=None)
Exemplo n.º 26
0
def main():
    TODOIST_API_TOKEN = get_todoist_token()
    EXISTIO_API_TOKEN = get_existio_token()
    if not TODOIST_API_TOKEN or not EXISTIO_API_TOKEN:
        logger.warning('Please set the API token in environment variable.')
        exit()
    api = TodoistAPI(TODOIST_API_TOKEN)
    api.sync()
    return api
Exemplo n.º 27
0
def create_shop_project(key=tovakey):
    api = TodoistAPI(key)
    api.sync()
    payload = request.get_json()
    shop_name = payload["context"]["facts"]["shop_name"]["grammar_entry"]
    added_project = api.projects.add(shop_name)
    api.commit()
    print("ADDED PROJECT:", added_project)
    return action_success_response()
Exemplo n.º 28
0
def totask(s: str, v: bool) -> (bool):
    if v: print("todoist - ", s)
    api = TodoistAPI(settings.TODOISTTOKEN)
    api.sync()
    inboxid = api.state['projects'][0]['id']
    task = api.quick.add('ChangeBattery: ' + s + " " + settings.TODOISTLABEL,
                         note='auto gen task',
                         remminder='tomorrow')
    True if task["id"] else False
Exemplo n.º 29
0
def get_id(name, component):
    """ Get the id for a specific name in a component"""
    api = TodoistAPI(get_token())
    api.sync()
    items = api.state[component]
    for item in items:
        if item['name'].lower() == name.lower():
            return item['id']

    # if we reach this point, the specified project does not exist
    raise RuntimeError('{} does not exist in {}!'.format(name, component))
Exemplo n.º 30
0
def find_project_by_name(name):
    """ Help function to find specific projects ID by given name

    Args:
        name ([str]): [Project name]
    """
    api = TodoistAPI(TODOIST_API_TOKEN)
    api.sync()
    projects = api.state['projects']
    for project in projects:
        if project['name'] == name:
            return (project['id'])
Exemplo n.º 31
0
def initialize_todoist():
    API_TOKEN = get_token()
    if not API_TOKEN:
        logging.warn('Please set the API token in environment variable.')
        exit()
    api = TodoistAPI(API_TOKEN)

    if api is None:
        return 'Request for Streaks with Todoist not authorized, exiting.'
    else:
        api.sync()
        return api
Exemplo n.º 32
0
    def useSyncLibrary(self):
        api = TodoistAPI(secrets.API_TOKEN)
        api.sync()
        #print(api.state['projects'])
        print("\n\n\n")
        items = api.state["items"]
        print(json.dumps(items))

        # get all completed
        all_comp = api.completed.get_all()

        for item in items:
            if item['checked'] > 0:
                print(item['content'])
Exemplo n.º 33
0
def setup_platform(hass, config, add_devices, discovery_info=None):
    """Set up the Todoist platform."""
    token = config.get(CONF_TOKEN)

    # Look up IDs based on (lowercase) names.
    project_id_lookup = {}
    label_id_lookup = {}

    from todoist.api import TodoistAPI
    api = TodoistAPI(token)
    api.sync()

    # Setup devices:
    # Grab all projects.
    projects = api.state[PROJECTS]

    # Grab all labels
    labels = api.state[LABELS]

    # Add all Todoist-defined projects.
    project_devices = []
    for project in projects:
        # Project is an object, not a dict!
        # Because of that, we convert what we need to a dict.
        project_data = {
            CONF_NAME: project[NAME],
            CONF_ID: project[ID]
        }
        project_devices.append(
            TodoistProjectDevice(hass, project_data, labels, api)
        )
        # Cache the names so we can easily look up name->ID.
        project_id_lookup[project[NAME].lower()] = project[ID]

    # Cache all label names
    for label in labels:
        label_id_lookup[label[NAME].lower()] = label[ID]

    # Check config for more projects.
    extra_projects = config.get(CONF_EXTRA_PROJECTS)
    for project in extra_projects:
        # Special filter: By date
        project_due_date = project.get(CONF_PROJECT_DUE_DATE)

        # Special filter: By label
        project_label_filter = project.get(CONF_PROJECT_LABEL_WHITELIST)

        # Special filter: By name
        # Names must be converted into IDs.
        project_name_filter = project.get(CONF_PROJECT_WHITELIST)
        project_id_filter = [
            project_id_lookup[project_name.lower()]
            for project_name in project_name_filter]

        # Create the custom project and add it to the devices array.
        project_devices.append(
            TodoistProjectDevice(
                hass, project, labels, api, project_due_date,
                project_label_filter, project_id_filter
            )
        )

    add_devices(project_devices)

    def handle_new_task(call):
        """Call when a user creates a new Todoist Task from HASS."""
        project_name = call.data[PROJECT_NAME]
        project_id = project_id_lookup[project_name]

        # Create the task
        item = api.items.add(call.data[CONTENT], project_id)

        if LABELS in call.data:
            task_labels = call.data[LABELS]
            label_ids = [
                label_id_lookup[label.lower()]
                for label in task_labels]
            item.update(labels=label_ids)

        if PRIORITY in call.data:
            item.update(priority=call.data[PRIORITY])

        if DUE_DATE in call.data:
            due_date = dt.parse_datetime(call.data[DUE_DATE])
            if due_date is None:
                due = dt.parse_date(call.data[DUE_DATE])
                due_date = datetime(due.year, due.month, due.day)
            # Format it in the manner Todoist expects
            due_date = dt.as_utc(due_date)
            date_format = '%Y-%m-%dT%H:%M'
            due_date = datetime.strftime(due_date, date_format)
            item.update(due_date_utc=due_date)
        # Commit changes
        api.commit()
        _LOGGER.debug("Created Todoist task: %s", call.data[CONTENT])

    hass.services.register(DOMAIN, SERVICE_NEW_TASK, handle_new_task,
                           schema=NEW_TASK_SERVICE_SCHEMA)
Exemplo n.º 34
0
def main():

    parser = argparse.ArgumentParser()
    parser.add_argument('-a', '--api_key', help='Todoist API Key')
    parser.add_argument('-l', '--label', help='The next action label to use', default='next_action')
    parser.add_argument('-d', '--delay', help='Specify the delay in seconds between syncs', default=5, type=int)
    parser.add_argument('--debug', help='Enable debugging', action='store_true')
    parser.add_argument('--inbox', help='The method the Inbox project should be processed',
                        default='parallel', choices=['parallel', 'serial'])
    parser.add_argument('--parallel_suffix', default='.')
    parser.add_argument('--serial_suffix', default='_')
    parser.add_argument('--hide_future', help='Hide future dated next actions until the specified number of days',
                        default=7, type=int)
    parser.add_argument('--onetime', help='Update Todoist once and exit', action='store_true')
    args = parser.parse_args()

    # Set debug
    if args.debug:
        log_level = logging.DEBUG
    else:
        log_level = logging.INFO
    logging.basicConfig(level=log_level)

    # Check we have a API key
    if not args.api_key:
        logging.error('No API key set, exiting...')
        sys.exit(1)

    # Run the initial sync
    logging.debug('Connecting to the Todoist API')
    api = TodoistAPI(token=args.api_key)
    logging.debug('Syncing the current state from the API')
    api.sync(resource_types=['projects', 'labels', 'items'])

    # 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', args.label, label_id)
    else:
        logging.error("Label %s doesn't exist, please create it or change TODOIST_NEXT_ACTION_LABEL.", args.label)
        sys.exit(1)

    def get_project_type(project_object):
        """Identifies how a project should be handled"""
        name = project_object['name'].strip()
        if project['name'] == 'Inbox':
            return args.inbox
        elif name[-1] == args.parallel_suffix:
            return 'parallel'
        elif name[-1] == args.serial_suffix:
            return 'serial'

    def get_item_type(item):
        """Identifies how a item with sub items should be handled"""
        name = item['content'].strip()
        if name[-1] == args.parallel_suffix:
            return 'parallel'
        elif name[-1] == args.serial_suffix:
            return 'serial'

    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)
            api.items.update(item['id'], labels=labels)

    def remove_label(item, label):
        if label in item['labels']:
            labels = item['labels']
            logging.debug('Updating %s without label', item['content'])
            labels.remove(label)
            api.items.update(item['id'], labels=labels)

    # Main loop
    while True:
        try:
            api.sync(resource_types=['projects', 'labels', 'items'])
        except Exception as e:
            logging.exception('Error trying to sync with Todoist API: %s' % str(e))
        else:
            for project in api.projects.all():
                project_type = get_project_type(project)
                if project_type:
                    logging.debug('Project %s being processed as %s', project['name'], project_type)

                    items = sorted(api.items.all(lambda x: x['project_id'] == project['id']), key=lambda x: x['item_order'])

                    for item in items:

                        # If its too far in the future, remove the next_action tag and skip
                        if args.hide_future > 0 and 'due_date_utc' in item.data and item['due_date_utc'] is not None:
                            due_date = datetime.strptime(item['due_date_utc'], '%a %d %b %Y %H:%M:%S +0000')
                            future_diff = (due_date - datetime.utcnow()).total_seconds()
                            if future_diff >= (args.hide_future * 86400):
                                remove_label(item, label_id)
                                continue

                        item_type = get_item_type(item)
                        child_items = get_subitems(items, item)
                        if item_type:
                            logging.debug('Identified %s as %s type', item['content'], item_type)

                        if item_type or len(child_items) > 0:
                            # Process serial tagged items
                            if item_type == 'serial':
                                for idx, child_item in enumerate(child_items):
                                    if idx == 0:
                                        add_label(child_item, label_id)
                                    else:
                                        remove_label(child_item, label_id)
                            # Process parallel tagged items or untagged parents
                            else:
                                for child_item in child_items:
                                    add_label(child_item, label_id)

                            # Remove the label from the parent
                            remove_label(item, label_id)

                        # Process items as per project type on indent 1 if untagged
                        else:
                            if item['indent'] == 1:
                                if project_type == 'serial':
                                    if item['item_order'] == 1:
                                        add_label(item, label_id)
                                    else:
                                        remove_label(item, label_id)
                                elif project_type == 'parallel':
                                    add_label(item, label_id)

            logging.debug('%d changes queued for sync... commiting if needed', len(api.queue))
            if len(api.queue):
                api.commit()

        if args.onetime:
            break
        logging.debug('Sleeping for %d seconds', args.delay)
        time.sleep(args.delay)
Exemplo n.º 35
0
class ServiceTodoist(ServicesMgr):

    def __init__(self, token=None, **kwargs):
        super(ServiceTodoist, self).__init__(token, **kwargs)
        self.AUTH_URL = 'https://todoist.com/oauth/authorize'
        self.ACC_TOKEN = 'https://todoist.com/oauth/access_token'
        self.REQ_TOKEN = 'https://todoist.com/oauth/access_token'
        self.consumer_key = settings.TH_TODOIST['client_id']
        self.consumer_secret = settings.TH_TODOIST['client_secret']
        self.scope = 'task:add,data:read,data:read_write'
        self.service = 'ServiceTodoist'
        self.oauth = 'oauth2'
        if token:
            self.token = token
            self.todoist = TodoistAPI(token)

    def read_data(self, **kwargs):
        """
            get the data from the service
            as the pocket service does not have any date
            in its API linked to the note,
            add the triggered date to the dict data
            thus the service will be triggered when data will be found

            :param kwargs: contain keyword args : trigger_id at least
            :type kwargs: dict

            :rtype: list
        """
        trigger_id = kwargs.get('trigger_id')
        date_triggered = kwargs.get('date_triggered')
        data = []
        project_name = 'Main Project'
        items = self.todoist.sync()
        for item in items.get('items'):
            date_added = arrow.get(item.get('date_added'),
                                   'ddd DD MMM YYYY HH:mm:ss ZZ')
            if date_added > date_triggered:
                for project in items.get('projects'):
                    if item.get('project_id') == project.get('id'):
                        project_name = project.get('name')
                data.append({'title': "From TodoIst Project {0}"
                                      ":".format(project_name),
                             'content': item.get('content')})
        cache.set('th_todoist_' + str(trigger_id), data)
        return data

    def save_data(self, trigger_id, **data):
        """
            let's save the data
            :param trigger_id: trigger ID from which to save data
            :param data: the data to check to be used and save
            :type trigger_id: int
            :type data:  dict
            :return: the status of the save statement
            :rtype: boolean
        """
        kwargs = {}

        title, content = super(ServiceTodoist, self).save_data(trigger_id,
                                                               data, **kwargs)

        if self.token:
            if title or content or \
                            (data.get('link') and len(data.get('link'))) > 0:
                content = title + ' ' + content + ' ' + data.get('link')

                self.todoist.add_item(content)

                sentence = str('todoist {} created').format(data.get('link'))
                logger.debug(sentence)
                status = True
            else:
                status = False
        else:
            logger.critical("no token or link provided for "
                            "trigger ID {} ".format(trigger_id))
            status = False
        return status
Exemplo n.º 36
0
class NextAction(object):
    def __init__(self):
        self.args = None
        self.api = None
        self.next_label_id = None
        self.waitfor_label_id = None
        self.active_label_id = None

    def main(self):
        self.setup()
        self.loop()

    def check_label(self, label):
        # Check if the label exists
        labels = self.api.labels.all(lambda x: x['name'] == label)
        if len(labels) > 0:
            label_id = labels[0]['id']
            logging.debug('Label %s found as label id %d', label, label_id)
            return label_id
        else:
            logging.error(
                "Label %s doesn't exist.", label)
            sys.exit(1)

    def setup(self):
        self.parse_args()

        # Run the initial sync
        logging.debug('Connecting to the Todoist API')
        self.api = TodoistAPI(token=self.args.api_key)
        logging.debug('Syncing the current state from the API')
        self.api.sync()
        self.next_label_id = self.check_label(self.args.label)
        self.active_label_id = self.check_label(self.args.active)
        self.waitfor_label_id = self.check_label(self.args.waitfor)

    def loop(self):
        """
        Main loop
        """
        while True:
            try:
                self.api.sync()
            except Exception as exc:
                logging.exception('Error trying to sync with Todoist API: %s',
                                  exc)
            else:
                self.process(self.api.projects.all())

                logging.debug(
                    '%d changes queued for sync... committing if needed',
                    len(self.api.queue))
                if len(self.api.queue):
                    self.api.commit()

            if self.args.onetime:
                break
            logging.debug('Sleeping for %d seconds', self.args.delay)
            time.sleep(self.args.delay)

    def process(self, projects, parent_indent=0, parent_type=None):
        """
        Process all projects
        """
        current_type = parent_type
        while projects and parent_indent < projects[0]["indent"]:
            # dig deeper
            if projects[0]["indent"] > parent_indent + 1:
                self.process(projects, parent_indent + 1, current_type)
                continue

            project = projects.pop(0)
            current_type = self.get_project_type(project, parent_type)
            if not current_type:
                # project not marked - not touching
                continue

            logging.debug('Project %s being processed as %s',
                          project['name'], current_type)

            def item_filter(x):
                return x['project_id'] == project['id']

            all_items = self.api.items.all(item_filter)
            items = sorted(all_items, key=lambda x: x['item_order'])
            item_objs = []
            while items:
                item_objs.append(Item(items))
            self.process_items(item_objs, current_type)
            self.activate(item_objs)

    def process_items(self, items, parent_type, not_in_first=False):
        """
        Process all tasks in project
        """
        # get the first item for serial
        first = None
        if parent_type == "serial":
            not_checked = [item for item in items if not item.checked]
            if not_checked and not self.is_waitfor(not_checked[0]):
                first = not_checked[0]

        # process items
        parent_active = False
        for item in items:
            current_type = self.get_item_type(item)

            if current_type:
                logging.debug('Identified %s as %s type', item.content,
                              current_type)
            else:
                current_type = parent_type

            if item.children:
                active = self.process_items(item.children, current_type,
                                            not_in_first or
                                            first and item != first)
            else:
                active = False

            if self.check_future(item):
                continue

            active |= self.process_item(item, parent_type, first, not_in_first)
            item.active = active
            parent_active |= active
        return parent_active

    def process_item(self, item, type, first=None, not_in_first=False):
        """
        Process single item
        """
        # untag if checked
        if item.checked or not_in_first:
            return self.remove_label(item, self.next_label_id)
        # don't tag if parent with unchecked child
        elif [child for child in item.children if not child.checked]:
            return self.remove_label(item, self.next_label_id)
        # tag all parallel but not waitfors
        elif type == "parallel" and not self.is_waitfor(item):
            return self.add_label(item, self.next_label_id)
        # tag the first serial
        elif type == "serial" and item == first:
            return self.add_label(item, self.next_label_id)
        # untag otherwise
        else:
            return self.remove_label(item, self.next_label_id)

    def activate(self, items):
        """
        Mark indent 1 items as active if supposed to be
        """
        for item in items:
            if item.active:
                self.add_label(item, self.active_label_id)
            else:
                self.remove_label(item, self.active_label_id)

    def check_future(self, item):
        """
        If its too far in the future, remove the next_action tag and skip
        """
        if self.args.hide_future > 0 and item.due_date_utc:
            due_date = datetime.strptime(item.due_date_utc,
                                         '%a %d %b %Y %H:%M:%S +0000')
            future_diff = (due_date - datetime.utcnow()).total_seconds()
            if future_diff >= (self.args.hide_future * 86400):
                self.remove_label(item, self.next_label_id)
                return True

    def get_project_type(self, project_object, parent_type):
        """
        Identifies how a project should be handled
        """
        name = project_object['name'].strip()
        if name == 'Inbox':
            return self.args.inbox
        elif name[-1] == self.args.parallel_suffix:
            return 'parallel'
        elif name[-1] == self.args.serial_suffix:
            return 'serial'
        elif parent_type:
            return parent_type

    def get_item_type(self, item):
        """
        Identifies how a item with sub items should be handled
        """
        name = item.content.strip()
        if name[-1] == self.args.parallel_suffix:
            return 'parallel'
        elif name[-1] == self.args.serial_suffix:
            return 'serial'

    def add_label(self, item, label):
        if label not in item.labels:
            labels = item.labels
            logging.debug('Updating %s with label %s', item.content, label)
            labels.append(label)
            self.api.items.update(item.id, labels=labels)
        return True

    def remove_label(self, item, label):
        if label in item.labels:
            labels = item.labels
            logging.debug('Updating %s without label %s', item.content, label)
            labels.remove(label)
            self.api.items.update(item.id, labels=labels)
        return False

    def is_waitfor(self, item):
        return self.waitfor_label_id in item.labels

    @staticmethod
    def get_subitems(items, parent_item=None):
        """
        Search a flat item list for child items
        """
        result_items = []
        found = False
        if parent_item:
            required_indent = parent_item['indent'] + 1
        else:
            required_indent = 1
        for item in items:
            if parent_item:
                if not found and item['id'] != parent_item['id']:
                    continue
                else:
                    found = True
                if item['indent'] == parent_item['indent'] and item['id'] != \
                        parent_item['id']:
                    return result_items
                elif item['indent'] == required_indent and found:
                    result_items.append(item)
            elif item['indent'] == required_indent:
                result_items.append(item)
        return result_items

    def parse_args(self):
        """
        Parse command-line arguments
        """
        parser = argparse.ArgumentParser()
        parser.add_argument('-a', '--api_key', help='Todoist API Key')
        parser.add_argument('-l', '--label',
                            help='The next action label to use',
                            default='next_action')
        parser.add_argument('-c', '--active',
                            help='The active level1 parent label',
                            default='active')
        parser.add_argument('-w', '--waitfor',
                            help='The waitfor label',
                            default='waitfor')
        parser.add_argument('-d', '--delay',
                            help='Specify the delay in seconds between syncs',
                            default=5, type=int)
        parser.add_argument('--debug', help='Enable debugging',
                            action='store_true')
        parser.add_argument('--inbox',
                            help='The method the Inbox project should '
                                 'be processed',
                            default='parallel', choices=['parallel', 'serial'])
        parser.add_argument('--parallel_suffix', default='.')
        parser.add_argument('--serial_suffix', default='_')
        parser.add_argument('--hide_future',
                            help='Hide future dated next actions until the '
                                 'specified number of days',
                            default=7, type=int)
        parser.add_argument('--onetime', help='Update Todoist once and exit',
                            action='store_true')
        self.args = parser.parse_args()

        # Set debug
        if self.args.debug:
            log_level = logging.DEBUG
        else:
            log_level = logging.INFO
        logging.basicConfig(level=log_level)

        # Check we have a API key
        if not self.args.api_key:
            logging.error('No API key set, exiting...')
            sys.exit(1)
Exemplo n.º 37
0
def main():
    """Main process function."""
    parser = argparse.ArgumentParser()
    parser.add_argument('-a', '--api_key', help='Todoist API Key')
    parser.add_argument('-l', '--label', help='The next action label to use', default='next_action')
    parser.add_argument('-d', '--delay', help='Specify the delay in seconds between syncs', default=5, type=int)
    parser.add_argument('--debug', help='Enable debugging', action='store_true')
    parser.add_argument('--inbox', help='The method the Inbox project should be processed',
                        default='parallel', choices=['parallel', 'serial', 'none'])
    parser.add_argument('--parallel_suffix', default='.')
    parser.add_argument('--serial_suffix', default='_')
    parser.add_argument('--hide_future', help='Hide future dated next actions until the specified number of days',
                        default=7, type=int)
    parser.add_argument('--hide_scheduled', help='', action='store_true')  # TODO: help
    parser.add_argument('--remove_label', help='Remove next action label from unmarked projects', action='store_true')
    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')
    args = parser.parse_args()

    # Set debug
    if args.debug:
        log_level = logging.DEBUG
    else:
        log_level = logging.INFO
    logging.basicConfig(level=log_level)

    # Check we have a API key
    if not args.api_key:
        logging.error('No API key set, exiting...')
        sys.exit(1)

    # 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)
    logging.debug('Syncing the current state from the API')
    api.sync()

    # 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', args.label, label_id)
    else:
        logging.error("Label %s doesn't exist, please create it or change TODOIST_NEXT_ACTION_LABEL.", args.label)
        sys.exit(1)

    def get_project_type(project_object):
        """Identifies how a project should be handled."""
        name = project_object['name'].strip()
        if name == 'Inbox' and args.inbox != 'none':
            return args.inbox
        elif name[-1] == args.parallel_suffix:
            return 'parallel'
        elif name[-1] == args.serial_suffix:
            return 'serial'

    def get_item_type(item):
        """Identifies how a item with sub items should be handled."""
        name = item['content'].strip()
        if name[-1] == args.parallel_suffix:
            return 'parallel'
        elif name[-1] == args.serial_suffix:
            return 'serial'

    def add_label(item, label):
        if label not in item['labels']:
            logging.debug('Updating %s (%d) with label', item['content'], item['id'])
            labels = item['labels']
            labels.append(label)
            api.items.update(item['id'], labels=labels)

    def remove_label(item, label):
        if label in item['labels']:
            logging.debug('Updating %s (%d) without label', item['content'], item['id'])
            labels = item['labels']
            labels.remove(label)
            api.items.update(item['id'], labels=labels)

    # Main loop
    while True:
        try:
            api.sync()
        except Exception as e:
            logging.exception('Error trying to sync with Todoist API: %s' % str(e))
        else:
            for project in api.projects.all(lambda x: not x['is_deleted'] and not x['is_archived']):
                project_type = get_project_type(project)
                items = api.items.all(
                            lambda x: x['project_id'] == project['id']
                                      and not (x['checked'] or x['is_deleted'] or x['is_archived'])
                )
                if project_type:
                    logging.debug('Project %s being processed as %s', project['name'], project_type)

                    # Get all items for the project, sort by the item_order field.
                    items = sorted(
                        items,
                        key=lambda x: x['item_order']
                    )

                    for real_order, item in enumerate(filter(lambda x: x['indent'] == 1, items)):
                        item_type = get_item_type(item)

                        if item.data.get('due_date_utc'):
                            if args.hide_scheduled and not item_type:
                                remove_label(item, label_id)
                                continue

                            # If its too far in the future, remove the next_action tag and skip
                            if args.hide_future > 0:
                                due_date = datetime.strptime(item['due_date_utc'], '%a %d %b %Y %H:%M:%S +0000')
                                future_diff = (due_date - datetime.utcnow()).total_seconds()
                                if future_diff >= (args.hide_future * 86400):
                                    remove_label(item, label_id)
                                    continue

                        child_items = get_subitems(items, item)

                        def add_indent1_label():
                            if child_items:
                                remove_label(item, label_id)
                                add_label(child_items[0], label_id)
                                func = remove_label if item_type == 'serial' else add_label
                                for child_item in child_items[1:]:
                                    func(child_item, label_id)
                            else:
                                add_label(item, label_id)

                        def remove_indent1_label():
                            remove_label(item, label_id)
                            for child_item in child_items:
                                remove_label(child_item, label_id)

                        if project_type == 'serial':
                            if real_order == 0:
                                add_indent1_label()
                            else:
                                remove_indent1_label()
                        elif project_type == 'parallel':
                            add_indent1_label()

                elif args.remove_label:
                    for item in items:
                        remove_label(item, label_id)

            if len(api.queue):
                logging.debug('%d changes queued for sync... commiting to Todoist.', len(api.queue))
                api.commit()
            else:
                logging.debug('No changes queued, skipping sync.')

        # If onetime is set, exit after first execution.
        if args.onetime:
            break

        logging.debug('Sleeping for %d seconds', args.delay)
        time.sleep(args.delay)