todoist-org-agenda: Todoist + Org Mode
O Todoist é um programa web para agendamento de tarefas, que permite criar “todo-lists” de forma simples e com sincronização entre dispositivos.
Eu descobri o Todoist em 2018, quando mergulhei no buraco negro que é a comunidade de “self-improvement” de hoje em dia. Nesse meio do caminho descobri algumas ferramentas que me ajudaram a me organizar, visto que na época me encontrava no início de um bacharelado em ciência da computação e precisava desesperadamente lidar com muitas coisas ao mesmo tempo. Desde então tenho organizado minhas semanas utilizando essa maravilhosa ferramenta (que só ficaria melhor se fosse software livre).
Em 2020 descobri uma ferramenta ainda mais maravilhosa para minha produtividade, o Emacs. Depois do entusiasmo inical de “configurar todas a coisas” descobri ferramentas que mudaram meu jeito de interagir com meu computador: passei a utilizar o Magit para meus repositórios git; elfeed para consumir notícias, blogs e vídeos por RSS; e o Org Mode, que tem sido uma mão na roda para tantas coisas.
O único problema do org é que ele está restrito ao emacs, então não poderia adicionar ou verificar tarefas em meu celular dessa forma. Por conta disso resolvi unir meu queridíssimo Todoist e sua facilidade ao Emacs e sua interface infinitamente adaptável. Assim comecei a escrever o todoist-org-agenda, um programinha para acessr a API do Todoist e armazenar minhas tarefas em Org.
Buscando tarefas⌗
O programa é bem simples, composto de uma simples função de chamada HTTP, uma interface com a API do Todoist e algumas funções auxiliares.
De início é necessário buscar as tarefas. A API do Todoist apresenta as tarefas divididas por projeto, de forma que é possível iterar pelos diferentes projetos e retornar as tarefas em objetos JSON
(defun mlemosf/todoist/get-todoist-tasks-by-project (project-id buffer)
"Get tasks from project given in PROJECT-ID and write them to buffer."
(let (
(project-tasks-url (format "https://api.todoist.com/rest/v1/tasks?project_id=%s" project-id))
(headers `(("Authorization" . ,(mlemosf/http/get-bearer-token "api.todoist.com")))))
(let (
(ids (seq-map (lambda (json-plist)
(plist-get json-plist :id))
(mlemosf/http/get-url-json-plist project-tasks-url headers)))
(task-url (format "https://api.todoist.com/rest/v1/tasks")))
(seq-doseq (task-id ids)
(let* (
(task (mlemosf/http/get-url-json-plist (format "%s/%s" task-url task-id) headers))
(due-date (plist-get (plist-get task :due) :date))
(id (plist-get task :id))
(origin "todoist")
(content (plist-get task :content))
(due-date-formatted (format-time-string "<%Y-%m-%d %a>" (parse-iso8601-time-string due-date)))
(created (format-time-string "[%Y-%m-%d %a %H:%M]" (parse-iso8601-time-string due-date))))
(princ (format "** TODO %s\nSCHEDULED: %s\n:PROPERTIES:\n:ID: %s\n:ORIGIN: %s\n:END:\n" content due-date-formatted id origin created) buffer))))))
A função itera por uma plist com os projetos, que é gerada a partir do JSON recebido (é bem mais tranquilo trabalhar com plists do que com JSON em elisp). A partir dos projetos o programa buscas as tarefas presentes em cada projeto, buscando o nome da tarefa, data limite e id. Esses dados são armazenados em um buffer do Org, para salvamento posterior.
Com as tarefas presentes em um buffer é possível salvá-las em um arquivo Org.
Fechando tarefas⌗
O mais legal do org é que os arquivos podem ser interpretados por diferentes programas, incluindo o Org Agenda. O Org Agenda organiza tarefas marcadas com TODO de acordo com a data das tarefas, e permite filtrar por dia, mês e ano (o que pra mim é perfeito, já que eu organizo minhas coisas toda semana, mas as vezes preciso mexer no meu cronograma).
O Org Agenda permite alterar aquele arquivo Org que eu criei para as tarefas em uma interface simplificada, com os próprios comandos, que eu uso pra reagenda e fechar as tarefas, marcando-as como DONE. E essas tarefas ficam em um buffer, perfeito para manipulações em emacs lisp :).
Com o buffer aberto, o programa busca todas as tarefas marcadas como DONE e as armazena em uma lista de IDs.
(defun mlemosf/todoist/get-todoist-done-ids ()
"Get list of all DONE tasks on org buffer"
(org-element-map (org-element-parse-buffer) 'headline
(lambda (x)
(let (
(todo-state (org-element-property :todo-type x))
(origin (org-element-property :ORIGIN x))
(id (org-element-property :ID x)))
(if (and
(eq todo-state 'done))
id)))))
Munido da lista de tarefas, o programa envia uma requisição para a API para fechar cada tarefa concluída. Dessa forma as tarefas ficam sincronizadas em outros dispositivos (apenas para leitura, quem sabe um dia eu implemento sincronização em tudo).
(defun mlemosf/todoist/close-done-tasks ()
(interactive)
(catch 'buffer-not-exist
(if (eq (get-buffer "todoist.org") nil)
(throw 'buffer-not-exist "todoist.org buffer is not open")
(with-current-buffer "todoist.org"
(let (
(tasks (mlemosf/todoist/get-todoist-done-ids)))
(progn
(dolist (task tasks)
(mlemosf/todoist/close-task task))
(set-buffer-modified-p -1)
(message "Tasks closed successfully")))))))
Assim eu consegui manter minhas tarefas globais no emacs, o que tem sido ótimo quando eu estou longe do meu computador.
O código para o projeto pode ser encontrado no meu github. Foi útil para mim e pode ser útil para outras pessoas, então resolvi tornar esse projetinho público (e como software livre). Sinta-se a vontade para deixar uma estrela se quiser.
Por hoje é só.
Happy hacking ;)