emacs-orgmode@gnu.org archives
 help / color / mirror / code / Atom feed
From: Patrick Brennan <pbrennan@gmail.com>
To: Patrick Brennan <pbrennan@gmail.com>, emacs-orgmode@gnu.org
Subject: Re: Google Tasks Integration
Date: Tue, 31 Jan 2012 10:31:03 -0800	[thread overview]
Message-ID: <CAB5xGOGw1Z6TGycpVJUACpq6eY-Mkx3Agt9J3QSSwQxPrF8oow@mail.gmail.com> (raw)
In-Reply-To: <8762fs9fsk.fsf@pinto.chemeng.ucl.ac.uk>


[-- Attachment #1.1: Type: text/plain, Size: 2358 bytes --]

Hi Eric,

Here is the script. It's pretty simple. There is no facility for
synchronizing an org-mode file as yet. I think the best way to do the sync
would be to do it when pushing an org-mode file to Google Tasks.

Tell me how you like this and if it works well for you.

Patrick

PS:

** Requirements:
  - Python 2.6. This is the version I am using, and I am not sure if an
earlier
    version will work.
  - setuptools. This is necessary in order to install the Google API for
Python.
    Get setuptools from here:
    http://pypi.python.org/pypi/setuptools#downloads
    I downloaded the egg package
    (
http://pypi.python.org/packages/2.6/s/setuptools/setuptools-0.6c11-py2.6.egg#md5=bfa92100bd772d5a213eedd356d64086
),
    set it to +x and run it
    sudo ./setuptools-0.6c11-py2.6.egg
  - Google Tasks API for Python.
    Use easy_install per this page:
    http://code.google.com/p/google-api-python-client/wiki/Installation
    sudo easy_install --upgrade google-api-python-client

Thanks,
Patrick

On Tue, Jan 31, 2012 at 5:12 AM, Eric S Fraga <e.fraga@ucl.ac.uk> wrote:

> Patrick Brennan <pbrennan@gmail.com> writes:
>
> > This weekend, while trying to avoid doing any real work, I started
> noodling
> > around with the Google Tasks API and I got a respectable distance toward
> a
> > script which will read your Google Tasks and export them to Org-mode.
> > Currently it will capture the task title, the notes, the todo status
> (TODO
> > or DONE) and the hierarchy, i.e. child tasks will be correctly placed
> under
> > their parents. There's still a lot of polish to apply, and of course,
> there
> > is no bidirectional capability as yet. Still, I wanted to send out this
> > notice in case anyone wanted to compare notes or thought it might be an
> > interesting application to share. The mobile apps for Google Tasks are
> > quite good, and if I can get a really good export going, I think this
> will
> > actually provide a plausible alternative workflow to the existing
> MobileOrg
> > flow.
> >
> > Patrick
>
> I would be very interested in this.  I have links to and from google
> calendar for appointments but could not figure out how to do either
> direction for TODO items.
>
> Thanks,
> eric
>
> --
> : Eric S Fraga (GnuPG: 0xC89193D8FFFCF67D) in Emacs 24.0.92.1
> : using Org-mode version 7.8.03 (release_7.8.03.283.g171ea)
>
>

[-- Attachment #1.2: Type: text/html, Size: 3661 bytes --]

[-- Attachment #2: TasksToOrgMode.py --]
[-- Type: text/x-python, Size: 9410 bytes --]

#! /usr/bin/python
import gflags
import httplib2
import math
import re

from apiclient.discovery import build
from oauth2client.file import Storage
from oauth2client.client import OAuth2WebServerFlow
from oauth2client.tools import run

########################################################################
# Algorithm from pg 7 of Peter Duffet-Smith, 
# Practical Astronomy With Your Calculator, 3d ed.
# Check against http://www.onlineconversion.com/julian_date.htm
# and <Orbiter distribution>/Utils/Date.exe
# This version does not check for Gregorian calendar or BC!!!
# This version does not handle fractional days!!!
def JulianDate (Year,Month,Day,modified=False):
    if (Month == 1)or(Month == 2):
        Yprime = Year - 1
        Mprime = Month + 12
    else:
        Yprime = Year
        Mprime = Month

    A = math.floor(Yprime / 100.0)
    B = 2.0 - A + math.floor(A / 4.0)

    C = math.floor(365.25 * Yprime)

    D = math.floor(30.6001 * (Mprime + 1.0))

    # Julian Day
    JD = B + C + D + Day + 1720994.5
    
    # Modified Julian Day
    MJD = B + C + D + Day - 679006.0
    
    if modified:
        return MJD
    else:
        return JD

########################################################################
# Given a year, month, and day, compute the day of the week
def DayOfWeek(Year,Month,Day):
    dayNames = [ 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat' ]
    mjd = JulianDate(Year,Month,Day,True)
    diff = mjd - 15019.0
    # dayIndex = math.floor(diff) % 7
    dayIndex = int(diff) % 7
    return dayNames[dayIndex]

########################################################################
# Given a string which represents a date and time, e.g.
# '2012-01-30T23:59:00Z' RFC 3339 timestamp
# Format it into a Org-Mode date.
def formatDateTimeStringToOrgMode(DateTimeString,angleBrackets=True):
    prog = re.compile('([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2})(.{1})')
    match = prog.match(DateTimeString)

    Year = int(match.group(1))
    Month = int(match.group(2))
    Day = int(match.group(3))
    ShortDayName = DayOfWeek(Year,Month,Day)
    Hour = int(match.group(4))
    Minute = int(match.group(5))
    Second = int(match.group(6))
    TimeZone = match.group(7) # TODO: Use the timezone somehow

    if angleBrackets:
        openBracket = '<'
        closeBracket = '>'
    else:
        openBracket = '['
        closeBracket = ']'

    retString = '%s%04d-%02d-%02d %s %02d:%02d:%02d%s' % \
        (openBracket, \
             Year, Month, Day, ShortDayName, Hour, Minute, Second, \
             closeBracket)

    return retString

########################################################################
# A simple utility function to print out a given attribute of a dict.
def printDictAttributeIfExists(dict,label,attr):
    try:
        print label + dict[attr]
    except KeyError:
        pass

########################################################################
# Print out all the fields of a task
def printTask(task):
    printDictAttributeIfExists(task,"    Task     : ", 'title')
    printDictAttributeIfExists(task,"    Kind     : ", 'kind')
    printDictAttributeIfExists(task,"    id       : ", 'id')
    printDictAttributeIfExists(task,"    etag     : ", 'etag')
    printDictAttributeIfExists(task,"    updated  : ", 'updated')
    printDictAttributeIfExists(task,"    selfLink : ", 'selfLink')
    printDictAttributeIfExists(task,"    position : ", 'position')
    printDictAttributeIfExists(task,"    parent   : ", 'parent')
    
    printDictAttributeIfExists(task,"    notes    : ", 'notes')
    printDictAttributeIfExists(task,"    status   : ", 'status')
    printDictAttributeIfExists(task,"    due      : ", 'due')
    printDictAttributeIfExists(task,"    completed: ", 'completed')
    printDictAttributeIfExists(task,"    deleted  : ", 'deleted')
    printDictAttributeIfExists(task,"    hidden   : ", 'hidden')
    print

########################################################################
# Given a list of nodes, which may themselves contain a list of nodes
# in their children attribute, find a node which has the given id and
# return it.
def findNode(nodeList, nodeId):
    for i in range(len(nodeList)):
        if (nodeList[i]['id'] == nodeId):
            return nodeList[i]
        if (len(nodeList[i]['children']) > 0):
            rv = (findNode(nodeList[i]['children'], nodeId))
            if (rv != False):
                return rv

    return False

########################################################################
# Given a list of nodes representing tasks, print out the tree in
# Org-mode format.
def printNodeList(nodeList, depth=0):
    stars = '*'
    for i in range(depth):
        stars = stars + '*'
    
    spaces = '  '
    for i in range(depth):
        spaces = spaces + ' '

    for i in range(len(nodeList)):
        node = nodeList[i]

        if (node['status'] == 'needsAction'):
            status = ' TODO '
        elif (node['status'] == 'completed'):
            status = ' DONE '
        else:
            status = ' '

        try:
            deadline = node['due']
        except KeyError:
            deadline = False

        try:
            notes = node['notes']
        except KeyError:
            notes = False

        # TODO: Add completed date
        try:
            completed = node['completed']
        except KeyError:
            completed = False

        # For recording when the item was closed, or the deadline
        dates_line = ''

        print stars + status + node['title']

        if (deadline or completed):
            dates_line = dates_line + spaces
        if (completed):
            dates_line = dates_line + \
                'CLOSED: ' + formatDateTimeStringToOrgMode(completed,False)
        if (completed and deadline):
            dates_line = dates_line + ' '
        if (deadline):
            dates_line = dates_line + 'DEADLINE: ' + \
                formatDateTimeStringToOrgMode(deadline)
        if (dates_line != ''):
            print dates_line

        # Properties. TODO: Any others?
        print spaces + ':PROPERTIES:'
        print spaces + ':ID:' + '       ' + node['id']
        print spaces + ':UPDATED:' + '  ' + node['updated']
        print spaces + ':END:'
            
        if (notes):
            noteslines = notes.split('\n')
            for line in noteslines:
                print spaces + line
                
        printNodeList(node['children'], depth+1)

        

FLAGS = gflags.FLAGS

# Set up a Flow object to be used if we need to authenticate. This
# sample uses OAuth 2.0, and we set up the OAuth2WebServerFlow with
# the information it needs to authenticate. Note that it is called
# the Web Server Flow, but it can also handle the flow for native
# applications
# The client_id and client_secret are copied from the API Access tab on
# the Google APIs Console
FLOW = OAuth2WebServerFlow(
    client_id='218130629659.apps.googleusercontent.com',
    client_secret='4/kMrW-AHZalDLvMnJlWTopIR2E1Cx',
    scope='https://www.googleapis.com/auth/tasks',
    user_agent='TasksToOrgMode/1.0')

# To disable the local server feature, uncomment the following line:
FLAGS.auth_local_webserver = False

# If the Credentials don't exist or are invalid, run through the native client
# flow. The Storage object will ensure that if successful the good
# Credentials will get written back to a file.
storage = Storage('tasks.dat')
credentials = storage.get()
if credentials is None or credentials.invalid == True:
  credentials = run(FLOW, storage)

# Create an httplib2.Http object to handle our HTTP requests and authorize it
# with our good Credentials.
http = httplib2.Http()
http = credentials.authorize(http)

# Build a service object for interacting with the API. Visit
# the Google APIs Console
# to get a developerKey for your own application.
service = build(serviceName='tasks', version='v1', http=http)

# Get all of the task lists
tasklists = service.tasklists().list().execute()

# Loop through the returned task lists
for tasklist in tasklists['items']:

    # Create a flat list of all the tasks.
    orgTaskList = {}
    orgTaskList['title'] = tasklist['title']
    orgTaskList['children'] = []
    
    # print 'Task List Title = ' + tasklist['title']
    # print 'Task List id = ' + tasklist['id']
    tasklist_id = tasklist['id']
    tasks = service.tasks().list(tasklist=tasklist_id).execute()
    
    # Loop through the tasks, add them to a flat list of tasks
    for task_item in tasks['items']:
        task_item_id = task_item['id']
        task = service.tasks().get(tasklist=tasklist_id,task=task_item_id).execute()
        task['children'] = []
        # print "Adding task " + task['title']
        orgTaskList['children'].append(task)
        
    # Now take tasks and add them to their correct parents
    i = 0
    while (i<len(orgTaskList['children'])):
        try:
            parentId = orgTaskList['children'][i]['parent']            
        except KeyError:
            # no parent
            parentId = ''
            i = i + 1
        if (parentId != ''):
            parentNode = findNode(orgTaskList['children'], parentId)
            if (parentNode):
                parentNode['children'].append(orgTaskList['children'].pop(i))

    print orgTaskList['title']
    print
    printNodeList(orgTaskList['children'])



  reply	other threads:[~2012-01-31 18:31 UTC|newest]

Thread overview: 15+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2012-01-30 19:33 Google Tasks Integration Patrick Brennan
2012-01-30 21:47 ` Philipp Haselwarter
2012-01-31 13:12 ` Eric S Fraga
2012-01-31 18:31   ` Patrick Brennan [this message]
2012-01-31 19:11   ` Simon Thum
2012-01-31 19:10     ` Eric S Fraga
2012-02-01 20:35       ` Simon Thum
2012-02-02 21:34         ` Matthew Jones
2012-02-03 21:45         ` Eric S Fraga
2012-01-31 15:18 ` Mehul Sanghvi
2012-01-31 19:07 ` Simon Thum
  -- strict thread matches above, loose matches on Subject: below --
2011-08-31 13:33 Google Tasks integration Brad Collette
2011-09-03 17:44 ` Sven Bretfeld
2011-09-06 16:09   ` Robert Goldman
2011-10-22 10:23 ` Bastien

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

  List information: https://www.orgmode.org/

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=CAB5xGOGw1Z6TGycpVJUACpq6eY-Mkx3Agt9J3QSSwQxPrF8oow@mail.gmail.com \
    --to=pbrennan@gmail.com \
    --cc=emacs-orgmode@gnu.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/emacs/org-mode.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).