Получение задач 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)





