papilio

Получение задач Google Tasks

Давненько я не писал в этот блог, хотя, конечно, не от того, что мне не о чем было писать или я перестал интересоваться web-разработкой и интернет-технологиями. Нет, скорее всего было просто лень :-)

Но чтобы эту лень побороть, надо пользоваться каким-нибудь планировщиком/календарём. А так как я полностью на продуктах Google, то неудивительно, что я пользуюсь Google Calendar (который замечательно синхронизируется как с системой, так и с телефоном) и теперь Google Tasks.

Итак, задача и проблема: мне необходимо вывести мои задачи Google Tasks прямо в консоли (это необходимо, например, чтобы потом повесить этот список задач прямо на мой рабочий стол, где уже есть Google Calendar). Как же это сделать, если пока нет Google Tasks API? Если нет, то нам придётся эмулировать работу браузера, чтобы получить список задач.

Для начала стоит выбрать верную версию сайта Google Tasks среди огромного множества (разные устройства, iGoogle, Gmail и т.п.). Я выбрал мобильную версию, так как можно напрямую обратиться к нужному списку:

  • https://mail.google.com/tasks/m для владельцев Google Accounts
  • https://mail.google.com/tasks/a/domain.com/m для пользователей Google Apps

Сначала я решил воспользоваться просто bash и написать скрипт. Но после того, как я довольно долго с этим промучался, я решил всё-таки писать на python, так как он есть у всех.

В итоге получился скрипт, который я немного прокомментирую чуть ниже (можно скачать отдельным файлом google_tasks.py):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
#!/usr/bin/env python
 
"""
This is a python script for retrieving Google Tasks 
Usage: 
    -e or --email [email] 
        Your Google Account or Google Apps email 
    -p or --password [password] 
        Your password 
    -b or --bullet [bullet] 
        Bullet for tasks list. Default is '*'
 
Example:
    google_tasks.py -e bob@gmail.com -p yaroslavl -b --
 
Thank you Scott Hillman for the implementation of Google Authentication
http://everydayscripting.blogspot.com/2009/10/python-fixes-to-google-login-script.html
 
Evgeny Pavlov, http://evgeny.tel
"""
 
import urllib
import urllib2
import htmllib
import getpass
import re
import sys
import getopt
 
def unescape(text):
    """Removes HTML or XML character references 
       and entities from a text string
 
       From Fredrik Lundh
       http://effbot.org/zone/re-sub.htm#unescape-html
 
       Little bit modified
    """
    def fixup(m):
        text = m.group(0)
        if text[:2] == "&#":
            # Character reference
            try:
                if text[:3] == "&#x":
                    return unichr(int(text[3:-1], 16))
                else:
                    return unichr(int(text[2:-1]))
            except ValueError:
                print "Error with encoding HTML entities"
                pass
        else:
            # Named entity
            text = unichr(htmlentitydefs.name2codepoint[text[1:-1]])
        return text # leave as is
    return re.sub("&#?\w+;", fixup, text)
 
def main(argv):
    """ Get arguments: email, password and type of bullet """
 
    bullet = '* '
    email =  ''
    password = ''
 
    try:
        opts, args = getopt.getopt(argv, "he:p:b:", ["help", "email=", "password=", "bullet="]) 
    except getopt.GetoptError:
        usage()
        sys.exit(2)
    for opt, arg in opts:
        if opt in ("-h", "--help"):
            usage()
            sys.exit()
        elif opt in ("-e", "--email"):
            email = arg
        elif opt in ("-p", "--password"):
            password = arg
        elif opt in ("-b", "--bullet"):
            bullet = arg    
 
    return (email, password, bullet)
 
def usage():
    """ Help and the list of arguments for this script """
 
    print """This is a python script for retrieving Google Tasks 
Usage: 
    -e or --email [email] 
        Your Google Account or Google Apps email 
    -p or --password [password] 
        Your password 
    -b or --bullet [bullet] 
        Bullet for tasks list. Default is '*'
 
Example:
    google_tasks.py -e bob@gmail.com -p yaroslavl -b --
 
Evgeny Pavlov, http://evgeny.tel"""
 
if __name__ == "__main__":
    # Arguments
    (email, password, bullet) = main(sys.argv[1:])
 
    # Google Account or Google Apps
    email_split = email.split('@')
    try:
        email_domain = email_split[1]
    except:
        print 'Incorrect email address!\n'
        usage()
        sys.exit()
    if email_domain in ('googlemail.com', 'gmail.com', 'google.com'):
        google_apps = 0
    else:
        google_apps = 1
        email = email_split[0]
 
    # Initialization  
    opener = urllib2.build_opener(urllib2.HTTPCookieProcessor())
    urllib2.install_opener(opener)
 
    # Define URLs
    if google_apps:
        login_page_url = 'https://www.google.com/a/%s/ServiceLogin' % email_domain
        auth_url = 'https://www.google.com/a/%s/LoginAction2' % email_domain
        tasks_url = 'https://mail.google.com/tasks/a/%s/m' % email_domain
    else:
        login_page_url = 'https://www.google.com/accounts/ServiceLogin'
        auth_url = 'https://www.google.com/accounts/ServiceLoginAuth'
        tasks_url = 'https://mail.google.com/tasks/m'
 
    # 1. Load login page
    login_page_content = opener.open(login_page_url).read()
 
    # Find GALX value
    galx_match_obj = re.search(r'name="GALX"\s*value="([^"]+)"', login_page_content, re.IGNORECASE)
 
    galx = galx_match_obj.group(1) if galx_match_obj.group(1) is not None else ''
 
    # Set up login credentials
    login_params = urllib.urlencode( {
       'Email' : email,
       'Passwd' : password,
       'continue' : tasks_url,
       'GALX': galx
    })
 
    # 2. Login
    opener.open(auth_url, login_params)
 
    # 3. Open Tasks home page
    tasks_content = opener.open(tasks_url).read()
 
    # Check signing in
    key = re.search('create_tasks', tasks_content)
    if not key:
        print 'Check your credintals!'
        sys.exit()
 
    # Retrieve list ids
    tasks_content_split_obj = re.search(r'<select(.*?)select>', tasks_content, re.IGNORECASE)
    tasks_content_split = tasks_content_split_obj.group(1)
    listids = re.findall(r'[0-9]{20}:[0-9]:[0-9]', tasks_content_split)
 
    # 4. Fetch all lists
    for listid in listids:
        # List content
        list_content = opener.open(tasks_url + "?listid=%s" % listid).read()
 
        # Only tasks remain
        list_content_split_obj = re.search('(.*?)name="numa"', list_content, re.IGNORECASE | re.DOTALL)
        list_content_split = list_content_split_obj.group(1)
 
        # Get Tasks in
<tr></tr>
tasks_in_tr = re.findall(r'
<tr(.*?)tr>', list_content_split, re.IGNORECASE | re.DOTALL)
 
        # Work with this dirty tasks
        for task_in_tr in tasks_in_tr:
            # Retrieve task
            task_obj = re.search(r'
<td class="text">(.*?)</td>
', task_in_tr, re.IGNORECASE)
            task = task_obj.group(1)
 
            # Indent
            indent = len(re.findall(r'
<td class="checkbox"', task_in_tr)) - 1
 
            # HTML entities
            task = unescape(task)
            task = task.strip()
 
            # 5. At last output
            if task  != '': 
                print '  ' * indent, bullet, task

Скрипт выводит список задач Google Tasks в довольно приятном виде.

Как же работает этот скрипт? Всё довольно просто: для того чтобы пройти аутентификацию Google, нужны адрес электронной почты и пароль. При этом Google проверяет, как происходит вход, поэтому каждый раз генерирует специальное значение GALX, которое затем сверяет. Но обо всём (почти обо всём по порядку):

  • 31-56, функция unescape (). Необходима для конвертации т.н. HTML entities, которые присутствуют в мобильной версии Google Tasks. Переводит #&123; в нормальные символы. Реализацию подсмотрел на W3C.
  • 59-80, функция main () нам нужна для того, чтобы прочесть аргументы, переданные в командной строке. Идея из онлайн-книги Dive Into Python.
  • 106-118, определение, каким является аккаунт — Google Accounts или Google Apps. Происходит путём банальной проверки домена.
  • 134-151, аутентификация Google как раз с использованием GALX. Кроме того, необходимо использовать cookies. Идея блога Every Day Scripting. У меня получалось реализовать и с помощью wget. Если кому-то интересно, то могу поделиться.
  • А дальше происходит анализ HTML и поиск задач.

Чтобы воспользоваться скриптом, надо его просто положить в нужное вам место, сделать исполняемым (chown +x) и запускать со следущими параметрами:

google_tasks.py -e bob@gmail.com -p yaroslavl

Кроме того, можно менять оформление задач с помощью ключа -b: по-умолчанию стоит звёзточка.

Метки: , , , ,



Есть комментарии (5):

  1. Vlad Ossipov @ 31/10/2009 в 11:35

    Евгений, посоветуй пожалуйста, как вывести календарь на рабочий стол? Уточню — я имею ввиду Win 7.

    • Evgeny Pavlov @ 02/11/2009 в 13:38

      Я знаю, как это сделать для Mac OS X и линукс. К сожалению, не обладаю данными по Win 7.

  2. Evgeny Pavlov @ 02/11/2009 в 16:32

    Небольшое обновление скрипта (были проблемы с кодировкой).

  3. wirtgen @ 19/01/2010 в 10:05

    А можно я скопирую этот пост к себе в блог ? На правах копи-паста. Активную гиперссылку на e-pavlov.ru конечно поставлю...

Оставить комментарий