├── .gitignore ├── README.md └── myTasks.py /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | 3 | myTasks is a python script for interacting with Google Tasks through the command line. Tasks can be displayed, added, marked as completed, cleared, or deleted. OAuth authentication is handled with some code based on Google's api examples. 4 | 5 | # Requirements 6 | 7 | ### Dependencies 8 | 9 | * myTasks uses `argparse` to manage command line args. For Python versions prior to 2.7, the [`argparse` module](http://pypi.python.org/pypi/argparse) must be installed with either pip or easy_install: `pip install argparse` or `easy_install argparse`. 10 | 11 | * myTasks requires the `google_api_python_client` [package](http://code.google.com/p/google-api-python-client/). Follow in download and installation instructions there. 12 | 13 | * myTasks as written also uses `keyring` to manage keys. [`keyring`](http://pypi.python.org/pypi/keyring) supports a number of OS keychain services, including OSX's Keychain. To install, use`pip` or `easy_install`: `pip install keyring`. myTasks uses `keyring` to store the client_secret provided by Google's [API Console](https://code.google.com/apis/console/). To store a key, from the python interpreter in the terminal, enter: 14 | `>>> import keyring` 15 | `>>> keyring.set_password('application name', 'username', 'password')` 16 | where password = your client_secret. 17 | 18 | ### Authorization 19 | 20 | As with other applications that require authentication to interact with google services, your Tasks app needs to be authorized to make requests. So, for initial set-up there are two things you must do. 21 | 22 | 1. Register your new application with google’s [API Console](https://code.google.com/apis/console/) by activating the Google Task API, and providing the requested information for a new app. This process will give you a ClientID, a ClientSecret Key, and an Application Developer Key that are necessary to authenticate the application. 23 | 24 | 2. Download and install the google API python developer’s library. I first installed using easy_install, but it didn’t work. So, you should use download the tar package, unzip it, open the terminal, change directory to the unzipped folder, and execute the command sudo python setup.py install. 25 | 26 | 3. Authenticate the application the first time you run the script from the command line. There will be a dialogue to approve access to your Tasks data. Paste the link provided into your browser, accept the access to your Tasks, then paste the provided key back into the terminal when asked for it. 27 | 28 | 29 | # Usage 30 | 31 | For help or reminders on usage, use the `-h` option. 32 | 33 | usage: tasks [option] arg1 arg2 arg3 34 | 35 | optional arguments: 36 | `-h`, --help show this help message and exit 37 | 38 | `-l [optional ListName]` 39 | 40 | * Lists tasks. For a single list, pass the list name. 41 | 42 | `-n [ListName "Task" YYYY-MM-DD]` 43 | 44 | * Adds new task. Pass the name of the task list and the new task as arguments in double quotes. For example: `tasks -n Main "My new task for the Main list."` There are several options for due dates when adding a new task. A due date can be omitted. To specify a specific date, add with iso format YYYY-MM-DD. For tasks with close due dates, you can enter `today`, `tomorrow`, `nextWeek`, or `nextMonth`, which will be converted to iso format dates. Or, for tasks due in the next week, the weekday name can be entered as `mon` or `monday` or `Monday`. Weekday designations are also converted to iso format dates. 45 | 46 | `-c` 47 | 48 | * Clears completed tasks from your lists. Takes no arguments. 49 | 50 | `-u [ListName TaskNumber]` 51 | 52 | 53 | * Updates a designated task as completed. Pass the name of the list and the number of the task. The number is available by first listing tasks with the `-l` command. For example: tasks -u Main 1. This command would mark the first message on the Main list as completed. 54 | 55 | `-d [ListName TaskNumber]` 56 | 57 | * Deletes a designated task. Pass the name of the list 58 | and the number of the task. The number is available by first listing tasks with the `-l` command. For example: tasks -d Main 1. This command would delete the first message from the Main list. 59 | 60 | `-R [old ListName new ListName]` 61 | 62 | * Renames a task list. Pass the old list name and the 63 | new list name. For example: `tasks -R Main Home`. This 64 | command would rename the Main list as the Home list. 65 | 66 | `-D [target listName]` 67 | 68 | * Delete a task list. Pass the targeted list name. For example: `tasks -D Main`. This command would delete the Main task list. 69 | 70 | 71 | For myself, I added an alias to my `.bash_profile` file pointing to the script named "tasks". So, listing tasks is `tasks -l`. 72 | 73 | -------------------------------------------------------------------------------- /myTasks.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import argparse 5 | 6 | import gflags 7 | import httplib2 8 | import keyring 9 | import sys 10 | import datetime 11 | 12 | 13 | 14 | from apiclient.discovery import build 15 | from oauth2client.file import Storage 16 | from oauth2client.client import OAuth2WebServerFlow 17 | from oauth2client.tools import run 18 | 19 | def today(): 20 | return todayDate.isoformat() 21 | 22 | def tomorrow(): 23 | return (todayDate + datetime.timedelta(days=1)).isoformat() 24 | 25 | def nextWeek(): 26 | return (todayDate + datetime.timedelta(days=7)).isoformat() 27 | 28 | def nextMonth(): 29 | return (todayDate + datetime.timedelta(days=30)).isoformat() 30 | 31 | def dueDate(due): 32 | due = due.lower() 33 | if weekdays.has_key(due) and weekdays[due] > todayDate.isoweekday(): 34 | diff = weekdays[due] - todayDate.isoweekday() 35 | dueDate = (todayDate + datetime.timedelta(diff)).isoformat() 36 | elif weekdays.has_key(due) and weekdays[due] <= todayDate.isoweekday(): 37 | diff = 6 - todayDate.isoweekday() 38 | dueDate = (todayDate + datetime.timedelta(diff)).isoformat() 39 | elif relDays.has_key(due): 40 | dueDate = relDays[due]() 41 | else: 42 | dueDate = due 43 | return dueDate+'T12:00:00.000Z' 44 | 45 | def tasks(listID): 46 | tasks = service.tasks().list(tasklist=listID).execute() 47 | n=1 48 | try: 49 | for task in tasks['items']: 50 | if task['title'] == '': pass 51 | else: 52 | taskName=task['title'] 53 | dueDate='No date.' 54 | if 'due' in task: 55 | fullDueDate=str(task['due']) 56 | dueDate=fullDueDate[:10] 57 | 58 | if 'parent' in task.keys(): 59 | task['taskNum'] = n 60 | print ' '+str(task['taskNum'])+'. '+task['title'].encode('utf-8', 'ignore')+' : '+dueDate 61 | n+=1 62 | else: 63 | task['taskNum'] = n 64 | print ' '+str(n)+'. '+task['title'].encode('utf-8', 'ignore')+' : '+dueDate 65 | n += 1 66 | except KeyError: print ' No tasks.' 67 | 68 | def listTasks(listName, tasklists): 69 | if listName == []: 70 | for tasklist in tasklists['items']: 71 | print tasklist['title'] 72 | listID=tasklist['id'] 73 | tasks(listID) 74 | print 75 | else: 76 | for tasklist in tasklists['items']: 77 | if tasklist['title'] != listName[0]: pass 78 | else: 79 | print tasklist['title'] 80 | listID=tasklist['id'] 81 | tasks(listID) 82 | 83 | 84 | def renameList(opts, tasklists): 85 | origList = opts[0] 86 | newList = opts[1] 87 | for tasklist in tasklists['items']: 88 | if tasklist['title'] == origList: 89 | tasklist['title'] = newList 90 | result = service.tasklists().update(tasklist=tasklist['id'], body=tasklist).execute() 91 | print origList+' renamed '+newList 92 | break 93 | 94 | def delList(opts, tasklists): 95 | listName = opts 96 | for tasklist in tasklists['items']: 97 | if tasklist['title'] == listName[0]: 98 | service.tasklists().delete(tasklist=tasklist['id']).execute() 99 | print listName, " deleted!" 100 | break 101 | 102 | def newTask(opts, tasklists): 103 | listName = opts[0] 104 | # dueDate = '' 105 | if len(opts) > 2: 106 | # if opts[2] == 'today': 107 | # dueDate = today.strftime('%Y-%m-%d') 108 | # elif opts[2] == 'tomorrow': 109 | # dueDate = tomorrow.strftime('%Y-%m-%d') 110 | # elif opts[2] == 'next week': 111 | # dueDate = nextWeek.strftime('%Y-%m-%d') 112 | # elif opts[2] == '2 weeks': 113 | # dueDate = twoWeeks.strftime('%Y-%m-%d') 114 | # elif opts[2] == 'next month': 115 | # dueDate = nextMonth.strftime('%Y-%m-%d') 116 | # else: dueDate = opts[2] 117 | # convertDue = dueDate+'T12:00:00.000Z' 118 | convertDue = dueDate(opts[2]) 119 | task = { 120 | 'title': opts[1], 121 | 'due': convertDue, 122 | } 123 | else: 124 | task = { 125 | 'title': opts[1] 126 | } 127 | listID = None 128 | for tasklist in tasklists['items']: 129 | if listName == tasklist['title']: 130 | listID=tasklist['id'] 131 | break 132 | if listID == None: 133 | tasklist = { 134 | 'title': listName, 135 | } 136 | result = service.tasklists().insert(body=tasklist).execute() 137 | listID = result['id'] 138 | newTask = service.tasks().insert(tasklist=listID, body=task).execute() 139 | print 'Completed.' 140 | 141 | def clearTask(tasklists): 142 | for tasklist in tasklists['items']: 143 | listID = tasklist['id'] 144 | service.tasks().clear(tasklist=listID, body='').execute() 145 | print 'Cleared.' 146 | 147 | def delTask(opts, tasklists): 148 | listName= opts[0] 149 | taskNumber = int(opts[1]) 150 | # match list off of list name 151 | listID = None 152 | for tasklist in tasklists['items']: 153 | if listName == tasklist['title']: 154 | listID=tasklist['id'] 155 | break 156 | # select and delete task 157 | tasks = service.tasks().list(tasklist=listID).execute() 158 | newList = tasks['items'] 159 | selectTask = newList[taskNumber-1] 160 | taskID = selectTask['id'] 161 | service.tasks().delete(tasklist=listID, task=taskID).execute() 162 | print "Completed." 163 | 164 | def updateTask(opts, tasklists): 165 | listName = opts[0] 166 | taskNumber = int(opts[1]) 167 | for tasklist in tasklists['items']: 168 | if listName == tasklist['title']: 169 | listID=tasklist['id'] 170 | break 171 | tasks = service.tasks().list(tasklist=listID).execute() 172 | newList = tasks['items'] 173 | selectTask = newList[taskNumber-1] 174 | taskID = selectTask['id'] 175 | chooseTask = service.tasks().get(tasklist=listID, task=taskID).execute() 176 | chooseTask['status'] = 'completed' 177 | markIt = service.tasks().update(tasklist=listID, task=chooseTask['id'], body=chooseTask).execute() 178 | print "Completed" 179 | 180 | 181 | relDays = {'today':today, 'tomorrow':tomorrow, 'nextWeek': nextWeek, 'nextMonth':nextMonth} 182 | 183 | weekdays = {'mon':0, 'tue':1, 'wed':2, 'thu':3, 'fri':4, 'sat':5, 'sun':6, 'monday' : 0, 'tuesday':1, 'wednesday':2,'thursday':3, 'friday':4, 'saturday':5, 'sunday':6 } 184 | 185 | todayDate = datetime.date.today() 186 | 187 | FLAGS = gflags.FLAGS 188 | 189 | # Set up a Flow object to be used if we need to authenticate. This 190 | # sample uses OAuth 2.0, and we set up the OAuth2WebServerFlow with 191 | # the information it needs to authenticate. Note that it is called 192 | # the Web Server Flow, but it can also handle the flow for native 193 | # applications 194 | # The client_id and client_secret are copied from the API Access tab on 195 | # the Google APIs Console 196 | FLOW = OAuth2WebServerFlow( 197 | client_id='XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX', 198 | client_secret=keyring.get_password('XXXXXXX', 'XXXXXXXX'), 199 | scope='https://www.googleapis.com/auth/tasks', 200 | user_agent='myTasks/v1') 201 | 202 | # To disable the local server feature, uncomment the following line: 203 | FLAGS.auth_local_webserver = False 204 | 205 | # If the Credentials don't exist or are invalid, run through the native client 206 | # flow. The Storage object will ensure that if successful the good 207 | # Credentials will get written back to a file. 208 | 209 | taskStore = "/PATH/TO/tasks.dat" 210 | storage = Storage(taskStore) 211 | credentials = storage.get() 212 | if credentials is None or credentials.invalid == True: 213 | credentials = run(FLOW, storage) 214 | 215 | # Create an httplib2.Http object to handle our HTTP requests and authorize it 216 | # with our good Credentials. 217 | http = httplib2.Http(cache=".cache") 218 | http = credentials.authorize(http) 219 | 220 | 221 | # Build a service object for interacting with the API. Visit 222 | # the Google APIs Console 223 | # to get a developerKey for your own application. 224 | service = build(serviceName='tasks', version='v1', http=http, 225 | developerKey=keyring.get_password('XXXXXXXXX', 'XXXXXXXXX')) 226 | 227 | parser = argparse.ArgumentParser(usage="tasks [option] arg1 arg2 arg3", 228 | prog="myTasks v0.3") 229 | 230 | parser.add_argument('-l', dest="tList", action='store', nargs="*", 231 | help='Lists tasks. For a sinlge list, pass the list name.') 232 | 233 | parser.add_argument('-n', dest="new", help='Adds new task. Pass the name of the task list and the \ 234 | new task as arguments in double quotes. For example: tasks -n Main "Add this task to the Main list."', 235 | action='store', metavar=' <"Task"> ', nargs="*") 236 | 237 | parser.add_argument('-c', dest="clear", action='store_true', default=False, 238 | help='Clears completed tasks from your lists. Takes no arguments.') 239 | 240 | parser.add_argument('-u', dest="update", help='Updates a designated task as completed. Pass the \ 241 | name of the list and the number of the task. The number is available by first listing tasks \ 242 | with the -l command. For example: tasks -u Main 1. This command would mark the first message \ 243 | on the Main list as completed.', action='store', metavar=' ', nargs="*") 244 | 245 | parser.add_argument('-d', dest="delTask", help='Deletes a designated task. Pass the name of the list and the \ 246 | number of the task. The number is available by first listing tasks with the -l command. \ 247 | For example: tasks -d Main 1. This command would delete the first message from the Main list.', 248 | action='store', metavar=' ', nargs="*") 249 | 250 | parser.add_argument('-R', dest="newList", help='Renames a task list. Pass the old list name and the \ 251 | new list name. For example: tasks -R Main Home. This command would rename the Main list as the Home \ 252 | list.', action='store', metavar=' ', nargs=2) 253 | 254 | parser.add_argument('-D', dest="delList", help='Delete a task list. Pass the targeted list name. \ 255 | For example: tasks -D Main. This command would delete the Main task list.', action='store', 256 | metavar='', nargs=1) 257 | 258 | 259 | tasklists = service.tasklists().list().execute() 260 | opts = parser.parse_args() 261 | 262 | 263 | if opts.new != None: 264 | newTask(opts.new, tasklists) 265 | elif opts.clear == True: 266 | clearTask(tasklists) 267 | elif opts.update != None: 268 | updateTask(opts.update, tasklists) 269 | elif opts.delTask != None: 270 | delTask(opts.delTask, tasklists) 271 | elif opts.newList != None: 272 | renameList(opts.newList, tasklists) 273 | elif opts.delList != None: 274 | delList(opts.delList, tasklists) 275 | elif opts.tList != None: 276 | listTasks(opts.tList, tasklists) 277 | 278 | 279 | 280 | --------------------------------------------------------------------------------