def test_add_indox_task(): g.db.close() content = 'Это новая задача!' debug_processing('{"type": "message_new", ' '"object": {"id": 43, "date": 1492522323, "out": 0, ' '"user_id": 481116745, "read_state": 0, ' '"body": "' + content + '"}}') acc = Subscription.get(Subscription.messenger_user_id == 481116745).account service = TodoistService() api = TodoistAPI(AccessToken.get(account=acc.id).token) api.sync() found_content = [ item['content'] for item in api.items.all() if item['content'] == content and item['project_id'] == service.project_name_to_id(api=api, proj_name='Inbox') ][0] assertEqual(funcname=test_add_indox_task.__name__, a=found_content, b=content) # assertEqual(vkapi.sended_message, 'Я не понял команды. Попробуйте еще раз.', __name__) service.delete_task(task=content, proj='Inbox', user_id=481116745)
def connect_to_todoist(self, api_token=None): if connected_to_internet(): if api_token is None: from dev.old.resources import default_token_path api_token = default_token_path if os.path.exists(api_token): from pyutils import get_token api_token = get_token(api_token) if True: print("WARNING - using mock to generate mock.") prototype = TodoistAPI(api_token) engine = 'pkl' # 'json' or 'pkl' from secrets import lib_root mock_path = os.path.join(lib_root, 'app_data', 'mock_TodoistAPI_py{}.{}'.format('3' if is_python_3() else '2', engine)) from pyutils import Mock self._api = Mock(prototype, dump_path=mock_path, dump_engine=engine) else: self._api = TodoistAPI(api_token) else: prototype = TodoistAPI() engine = 'pkl' # 'json' or 'pkl' from secrets import lib_root mock_path = os.path.join(lib_root, 'app_data', 'mock_TodoistAPI_py{}.{}'.format('3' if is_python_3() else '2', engine)) from pyutils import Mock self._api = Mock(prototype, dump_path=mock_path, dump_engine=engine)
class TaskManager: def __init__(self, settings): self.todoist = TodoistAPI(settings['API_KEY']) self.todoist.sync() self.user = self.todoist.state['user']['id'] self.labels = convert_labels(self.todoist.state['labels']) def _sync(func): def synced(self, *args, **kwargs): self.todoist.sync() return func(self, *args, **kwargs) return synced @_sync def avg_spoons(self): completed_spoonsy_tasks = [ task for task in self.get_tasks() if is_spoonsy_task(task, self.labels) and task['date_completed'] ] return mean([ calculate_values(task['labels']) for task in completed_spoonsy_tasks ]) @_sync def get_projects(self): return [ project for project in self.todoist.state['projects'] if project_owned_by_user(project, self.user) ] @_sync def get_tasks(self, project_name='all'): all_tasks = self.todoist.state['items'] if project_name == 'all': return all_tasks else: project = get_project_or_none(all_tasks, project_name) return [ item for item in all_tasks if item['project_id'] == project['id'] ]
class Todoist: def __init__(self): self.token = token self.api = TodoistAPI(self.token) self.api.sync() def sync(self): self.api.sync() def get_obj(self, obj): if obj == 'state': return self.api.state elif obj == 'projects': return self.api.state['projects'] elif obj == 'labels': return self.api.state['labels'] elif obj == 'items': return self.api.state['items'] elif obj == 'sections': return self.api.state['sections'] else: return 'Object Not Found' def get_items(self, p_id=False): all_items = self.get_obj('items') if p_id: select_items = [] for item in all_items: if item['project_id'] == p_id: select_items.append(item) all_items = select_items return all_items
def initiate_api(access_token): """Initiate and sync Todoist API""" api = TodoistAPI(access_token) api.sync() if bool(api['user']): return api else: return None
def add_task(task, project='', date_string='', accuracy='day', due_datetime=''): g.db.close() import time time.sleep(1) body = '{0}{1}{2}.'.format(task, ' в ' + project if project != '' else '', '. ' + date_string if datetime != '' else '') debug_processing('{"type": "message_new", ' '"object": {"id": 43, "date": 1492522323, "out": 0, ' '"user_id": 481116745, "read_state": 0, ' '"body": "' + body + '"}}') time.sleep(1) if 'Все верно?' in vkapi.sended_message: # bad debug_processing('{"type": "message_new", ' '"object": {"id": 43, "date": 1492522323, "out": 0, ' '"user_id": 481116745, "read_state": 0, ' '"body": "Ага!"}}') time.sleep(1) acc = Subscription.get(Subscription.messenger_user_id == 481116745).account service = TodoistService() api = TodoistAPI(AccessToken.get(account=acc.id).token) api.sync() project = 'Inbox' if project == '' else project try: found_content = [ item for item in api.items.all() if item['content'] == task + '.' and item['project_id'] == service.project_name_to_id(api=api, proj_name=project) ][0] except Exception as e: print(api.items.all()) found_content = [ item for item in api.items.all() if item['content'] == task + '.' and item['project_id'] == service.project_name_to_id(api=api, proj_name=project) ][0] assertEqual(funcname=add_task.__name__, a=found_content.data['content'], b=task + '.') # import datetime as dt # timedelts = {'day': dt.timedelta(days=1), 'hour': dt.timedelta(hours=1)} # assertTrue(due_datetime < found_content['due_date_utc'] + timedelts[accuracy] # and due_datetime > found_content['due_date_utc'] - timedelts[accuracy], funcname=add_task.__name__) service.delete_task(task=task + '.', proj=project, user_id=481116745)
def openTodoist(): f = open('config.json',) config = json.load(f) f.close() if config['api-token']=='': print('Please add an API token to config.json') exit() api = TodoistAPI(token=config['api-token']) api.sync() return api
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 add_tasks_todoist(project_name: str, tasks: List[Task]): # Authenticate with the todoist API api_token = open("todoist_token.txt").read() api = TodoistAPI(api_token) api.sync() # Find the uni project or create it projects = api.state["projects"] projects = list(filter(lambda p: p["name"] == project_name, projects)) if len(projects) != 0: project_id = projects[0]["id"] else: procject = api.projects.add(project_name) api.commit() project_id = procject["id"] # Add the tasks for task in tasks: api.items.add( task.text, project_id=project_id, due={"date": task.duedate.strftime("%Y-%m-%d")}, ) print(task) api.commit()
def __init__(self): token, fromfile = self._load_token() if not fromfile: # if prompting for token, already interactive, so # show confirmation if the task is added. self.verbose = True self._api = TodoistAPI(token)
def init_api(self): if not self.is_authorized(): return try: with app.app_context(): self.api = TodoistAPI(self.auth, cache=app.config['TODOIST']['CACHE']) result = self.api.sync() if 'error' in result: raise SyncError if 'user' in result and not self.todoist_id: self.todoist_id = result['user']['id'] except SyncError: self.auth = '' self.todoist_id = None self.api = None
def api_init(): """ Initialises the API and retrieves the projects. :return: a dictionary of projects. :rtype: dict """ global __api __api = TodoistAPI(secret())
def __init__(self, apiToken: str): """ Initialize common protected properties Args: apiToken: The login token for the todoist API """ self._todoist: TodoistAPI = TodoistAPI(apiToken) self._preferences: Preferences = Preferences() self._devTasks: Tasks = Tasks([])
class User(db.Model): id = db.Column(db.Integer, primary_key=True) tg_id = db.Column(db.BigInteger, unique=True) first_name = db.Column(db.String(255)) last_name = db.Column(db.String(255)) username = db.Column(db.String(100)) todoist_id = db.Column(db.BigInteger, nullable=True, index=True) state = db.Column(db.String(36), default='', index=True) auth = db.Column(db.String(255), default='') is_active = db.Column(db.Boolean, default=True) created_at = db.Column(db.DateTime, nullable=False) last_active_at = db.Column(db.DateTime, nullable=True) # noinspection PyTypeChecker def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.update = None # type: Update self.message = None # type: Message self.api = None # type: TodoistAPI self.now = None # type: datetime def is_authorized(self): return bool(self.auth) def first_init_api(self): self.init_api() if not self.is_authorized(): return from app.telegram.handlers import bot bot().base_welcome(self) def init_api(self): if not self.is_authorized(): return try: with app.app_context(): self.api = TodoistAPI(self.auth, cache=app.config['TODOIST']['CACHE']) result = self.api.sync() if 'error' in result: raise SyncError if 'user' in result and not self.todoist_id: self.todoist_id = result['user']['id'] except SyncError: self.auth = '' self.todoist_id = None self.api = None def send_message(self, text, **kwargs): from app.telegram.handlers import bot try: return bot().send_message(self.tg_id, text, **kwargs) except Unauthorized: self.is_active = False db.session.add(self) db.session.commit() return False
class TodoistItems: def __init__(self, token): self.api_token = token self.api = TodoistAPI(self.api_token) self.api.sync() def key_check(self, item, key): try: result = item[key] return result except: return False def date_check(self, item, date): if self.key_check(item, 'due'): if item['due']['lang'] == 'ja': if date.replace('/', '-') in item['due']['date']: return True elif '毎日' in item['due']['string']: return True else: try: if date == parse(item['due']['date']).strftime('%Y/%m/%d'): return True except ValueError: pass return False def get_project_name(self, item): if self.key_check(item, 'project_id'): project_id = item['project_id'] else: project_id = item['parent_id'] project = self.api.projects.get_by_id(project_id) return project['name'] def find_by_date(self, date): return [ item for item in self.api['items'] if self.date_check(item, date) ]
def todoist_reading_list(handler=None): todoist = TodoistAPI(get_credential('todoist_token')) todoist.sync() reading_list = t_utils.get_project_by_name(todoist, 'reading list') categories = t_utils.get_child_projects(todoist, reading_list) for task in todoist.state['items']: for project in categories: if task['project_id'] == project['id']: content = task['content'] logging.info(content) m = re.search(r'\[([^\[]+)\]\((.*)\)', content) # The todoist app stores links as either a markdown formatted link or "title - url" # if markdown links fail, try to parse the "title - url" format. if m: logging.info("markdown group") title = m.group(1) url = m.group(2) logging.info(title) logging.info(url) else: logging.info("hyphen group") content_components = content.split(" - ") if len(content_components) > 1: title = ''.join(content_components[:-1]) url = content_components[-1].strip() logging.info(title) logging.info(url) else: task.update(content="FAILED TO PARSE: " + content) task.move(parent_id=reading_list['id']) comments = t_utils.get_comments_for_task(todoist, task) article = article_parser.parse_url(url) data = { 'url': url, 'title': title, 'summary': article.summary, 'keywords': article.keywords, 'text': article.text, 'published_date': article.publish_date, 'notes': comments, 'category': project['name'] } handler.publish('archive_article', url, data) task.complete() todoist.commit()
def subscribe(self, chat_id: int, todoist_key: str): subscribed = any(sub.chat_id == chat_id for sub in self._subscriptions) if subscribed: return with self._lock: self._subscriptions.append( Subscription(chat_id, TodoistAPI(token=todoist_key, cache=None))) self._redis.lpush( redis_keys.subscription, dumps({ 'chat_id': chat_id, 'todoist_key': todoist_key })) logging.info(f'User with telegram id {chat_id} subcribed')
def add_issue_to_todoist(event=None, handler=None): todoist = TodoistAPI(get_credential('todoist_token')) todoist.sync() issue_project = t_utils.get_project_by_name(todoist, 'issues') r = event.data todoist.items.add( '{} [#{}]({})'.format(r['title'], r['number'], r['html_url']), project_id=issue_project['id'], ) # todo close todos for prs/issues that are no longer active todoist.commit()
def add_mention_to_todoist(event=None, handler=None): todoist = TodoistAPI(get_credential('todoist_token')) todoist.sync() mention_project = t_utils.get_project_by_name(todoist, 'GH Mentions') r = event.data todoist.items.add( 'GH Mention - {} [#{}]({})'.format(r['title'], r['number'], r['url']), # project_id=mention_project['id'], # auto_reminder=True, # due={"string": "next workday at 9am"}, priority=4) # todo close todos for prs/issues that are no longer active todoist.commit()
def load_subscriptions_store(redis: Redis): try: subscription_jsons = redis.lrange(redis_keys.subscription, 0, -1) subscription_dicts = [loads(sjs) for sjs in subscription_jsons] subscriptions = [ Subscription(chat_id=sdict['chat_id'], todoist_api=TodoistAPI( token=sdict['todoist_key'], cache=None, api_endpoint='https://api.todoist.com')) for sdict in subscription_dicts ] logging.info('Successfully loaded notifications from redis.') return SubscriptionsStore(redis=redis, initial_subscriptions=subscriptions) except JSONDecodeError as ex: logging.error(ex) logging.error( 'Could not load subscriptions from redis. Initializing with empty list instead' ) return SubscriptionsStore(redis=redis)
def add_issue_to_todoist(event=None, handler=None): todoist = TodoistAPI(get_credential('todoist_token')) todoist.sync() issue_project = None for p in todoist.state['projects']: if p['name'].lower() == 'issues': issue_project = p break r = event.data todoist.items.add( '{} [#{}]({})'.format(r.title, r.number , r.html_url), project_id=issue_project['id'], ) # todo close todos for prs/issues that are no longer active todoist.commit()
def add_pr_to_todoist(event=None, handler=None): todoist = TodoistAPI(get_credential('todoist_token')) todoist.sync() review_project = None for p in todoist.state['projects']: if p['name'].lower() == 'review requests': review_project = p break r = event.data todoist.items.add('{} [{} #{}]({})'.format(r.title, r.base.repo.full_name, r.number, r.html_url), project_id=review_project['id'], auto_reminder=True, due={"string": "next workday at 9am"}, priority=4) # todo close todos for prs/issues that are no longer active todoist.commit()
def main(args=None): """Console script for greminders2todoist.""" flow = MyInstalledAppFlow.from_client_secrets_file('client.json', SCOPES) creds: Credentials = flow.run_local_server(port=3423) api = TodoistAPI(creds.token) api.sync() tree: _ElementTree = etree.parse(open('./Reminders.html'), etree.HTMLParser()) pprint(scan_fields(tree)) rows = [ dict(type='task', content=task.title, priority=4, indent=None, author=None, responsible=None, date=proc_date(task), date_lang='en', timezone=None) for task in gen_tasks(tree) if task.state != 'archived' and ( task.recurrence or task.due and task.due > datetime.now()) ] with open('out.csv', 'w') as outfile: writer = csv.DictWriter(outfile, [ 'type', 'content', 'priority', 'indent', 'author', 'responsible', 'date', 'date_lang', 'timezone' ]) writer.writeheader() writer.writerows(rows) [inbox] = [p for p in api.state['projects'] if p['name'] == 'Inbox'] for task in rows: api.items.add(task['content'], inbox['id'], date_string=task['date']) api.commit() return 0
def add_pr_to_todoist(event=None, handler=None): todoist = TodoistAPI(get_credential('todoist_token')) todoist.sync() # review_project = t_utils.get_project_by_name(todoist, 'review requests') r = event.data title = 'PR' if r['base']['repo']['full_name'] == 'pulp/pulp_ansible': title = 'Pulp Ansible PR' todoist.items.add( '{} - {} [{} #{}]({})'.format(title, r['title'], r['base']['repo']['full_name'], r['number'], r['html_url']), # project_id=review_project['id'], # auto_reminder=True, # due={"string": "next workday at 9am"}, # priority=4 ) # todo close todos for prs/issues that are no longer active todoist.commit()
def __init__(self, api_token: str) -> None: super().__init__() self.api = TodoistAPI(api_token) self.data: JsonDict = {} self.projects: DictProjectId = {} self._allow_creation = False
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['trigger_id'] data = list() cache.set('th_todoist_' + str(trigger_id), 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
class Todoist(SingletonMixin): """A wrapper for the Todoist API.""" def __init__(self, api_token: str) -> None: super().__init__() self.api = TodoistAPI(api_token) self.data: JsonDict = {} self.projects: DictProjectId = {} self._allow_creation = False def smart_sync(self): """Only perform a full resync if needed.""" if not self.data.get("projects", {}): # If internal data has no projects, reset the state and a full (slow) sync will be performed. self.api.reset_state() partial_data = {} for attempt in range(3): # For some reason, sometimes this sync() method returns an empty string instead of a dict. # In this case, let's try again for a few times until we get a dictionary. partial_data = self.api.sync() if isinstance(partial_data, dict): break LOGGER.warning(f"Retrying, attempt {attempt + 1}: partial_data is not a dict(): {partial_data!r}") self._merge_new_data(partial_data) self.projects = dict(PROJECTS_NAME_ID_JMEX.search(self.data)) def _merge_new_data(self, partial_data: JsonDict): if not self.data: self.data = partial_data return for key, value in partial_data.items(): if isinstance(value, list): if key not in self.data: self.data[key] = [] self.data[key].extend(value) elif isinstance(value, dict): if key not in self.data: self.data[key] = {} self.data[key].update(value) else: self.data[key] = value def keys(self): """Keys of the data.""" return sorted(self.data.keys()) @deprecated(reason="use find* functions instead") def fetch( self, element_name: str, return_field: str = None, filters: JsonDict = None, index: int = None, matching_function=all, ) -> List[Any]: """Fetch elements matching items that satisfy the desired parameters. :param element_name: Name of the element to search. E.g. 'projects', 'items'. :param return_field: Name of the return field. If None, return the whole element. :param filters: Parameters for the search. :param index: Desired index to be returned. If nothing was found, return None. :param matching_function: ``all`` items by default, but ``any`` can be used as well. """ if not filters: values_to_list: JsonDict = {} else: values_to_list = {key: [value] if not isinstance(value, list) else value for key, value in filters.items()} found_elements = [ element[return_field] if return_field else element for element in self.data[element_name] if not filters or matching_function(element[key] in value for key, value in values_to_list.items()) ] if index is not None: return found_elements[index] if found_elements else None return found_elements @deprecated(reason="use find* functions instead") def fetch_first(self, element_name: str, return_field: str = None, filters: JsonDict = None) -> Optional[Any]: """Fetch only the first result from the fetched list, or None if the list is empty.""" return self.fetch(element_name, return_field, filters, 0) def find_project_id(self, exact_name: str) -> Optional[int]: """Find a project ID by its exact name. :param exact_name: Exact name of a project. """ return self.projects.get(exact_name, None) def find_projects(self, partial_name: str = "") -> DictProjectId: """Find projects by partial name. :param partial_name: Partial name of a project. """ return { name: project_id for name, project_id in self.projects.items() if partial_name.casefold() in name.casefold() } def find_project_items(self, exact_project_name: str, extra_jmes_expression: str = "") -> List[JsonDict]: """Fetch all project items by the exact project name. :param exact_project_name: Exact name of a project. :param extra_jmes_expression: Extra JMESPath expression to filter fields, for instance. """ project_id = self.find_project_id(exact_project_name) if not project_id: return [] return jmespath.search(f"items[?project_id==`{project_id}`]{extra_jmes_expression}", self.data) def find_items_by_content(self, exact_project_name: str, partial_content: str) -> List[JsonDict]: """Return items of a project by partial content. :param exact_project_name: Exact name of a project. :param partial_content: Partial content of an item. """ clean_content = partial_content.casefold() return [ item for item in self.find_project_items(exact_project_name) if clean_content in item.get("content", "").casefold() ]
def __init__(self, settings): self.todoist = TodoistAPI(settings['API_KEY']) self.todoist.sync() self.user = self.todoist.state['user']['id'] self.labels = convert_labels(self.todoist.state['labels'])
def get_todoist_client(): """ The Todoist client isn't thread safe, so we need to create it each time we want to use it """ return TodoistAPI(TODOIST_TOKEN)
def __init__(self, conf): self.todoist = TodoistAPI(conf['secret']) self.conf = conf self.todoist.sync()
import config import urwid from todoist import TodoistAPI import pprint token = config.todoist_api_key api = TodoistAPI(token) api.sync() items = api.state['items'] pprint.pprint(len(items)) for item in items: # pprint.pprint(item, indent=4) # print('++++++++++++++++++++++++++++++++++') pprint.pprint('Content: ' + item['content'], indent=4) pprint.pprint('Date Completed: ' + str(item['date_completed']), indent=4) pprint.pprint('Checked: ' + str(item['checked']), indent=4) # pprint.pprint('Due Date: ' + item['due']['date'], indent=4) print( '////////////////////////////////////////////////////////////////////////////////////\n' ) def exit_on_q(key): if key in ('q', 'Q'): raise urwid.ExitMainLoop() palette1 = [('tl_txt_pal', 'light magenta', 'black'), ('tr_txt_pal', 'light blue', 'black'),
def _api_for_user(self, user_id): acc = Subscription.get( Subscription.messenger_user_id == user_id).account api = TodoistAPI(AccessToken.get(AccessToken.account == acc).token) api.sync() return api
class TodoistWrapper: def __init__(self, conf): self.todoist = TodoistAPI(conf['secret']) self.conf = conf self.todoist.sync() def get_projects(self): projs = [Project(self, p['id']) for p in self.todoist['projects']] if not self.conf['show_inbox']: return [proj for proj in projs if proj.name != 'Inbox'] else: return projs def project_data(self, project_id): return self.todoist.projects.get_data(project_id) def task_data(self, task_id): return self.todoist.items.get(task_id) def create_task(self, name, project_id): self.todoist.items.add(name, project_id) self.todoist.commit() def create_project(self, project_name): self.todoist.projects.add(project_name) self.todoist.commit() def complete_task(self, task_id): try: self.todoist.items.complete([int(task_id)]) self.todoist.commit() except ValueError: raise CmdError("Argument must be a task id.") def _get_project_task_ids(self, project_id): try: project = Project(self, int(project_id)) task_ids = [task.obj_id for task in project] return task_ids except ValueError: raise CmdError("Argument must be a project id.") def complete_project(self, project_id): self.todoist.items.complete(self._get_project_task_ids(project_id)) self.todoist.commit() def clear_project(self, project_id): self.todoist.items.delete(self._get_project_task_ids(project_id)) self.todoist.commit() def delete_project(self, project_id): self.todoist.projects.delete([project_id]) self.todoist.commit()