This repository is private.
All pages are served over SSL and all pushing and pulling is done over SSH.
No one may fork, clone, or view it unless they are added as a member.
Every repository with this icon (
) is private.
Every repository with this icon (
This repository is public.
Anyone may fork, clone, or view it.
Every repository with this icon (
) is public.
Every repository with this icon (
Brad Fitzpatrick (author)
Sat May 09 15:26:39 -0700 2009
addressbooker / addressbooker.py
| 0ed12f30 » | Brad Fitzpatrick | 2008-11-28 | 1 | # Copyright (C) 2008 Brad Fitzpatrick | |
| 2 | # | ||||
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| 4 | # you may not use this file except in compliance with the License. | ||||
| 5 | # You may obtain a copy of the License at | ||||
| 6 | # | ||||
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 | ||||
| 8 | # | ||||
| 9 | # Unless required by applicable law or agreed to in writing, software | ||||
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, | ||||
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| 12 | # See the License for the specific language governing permissions and | ||||
| 13 | # limitations under the License. | ||||
| 14 | |||||
| 15 | |||||
| 16 | __author__ = 'brad@danga.com (Brad Fitzpatrick)' | ||||
| 17 | |||||
| 04acea54 » | Brad Fitzpatrick | 2008-11-29 | 18 | # Core Python | |
| f3be64f3 » | Brad Fitzpatrick | 2008-11-29 | 19 | import cgi | |
| 20 | import logging | ||||
| 04acea54 » | Brad Fitzpatrick | 2008-11-29 | 21 | import pprint | |
| f3be64f3 » | Brad Fitzpatrick | 2008-11-29 | 22 | import random | |
| 04acea54 » | Brad Fitzpatrick | 2008-11-29 | 23 | import re | |
| f3be64f3 » | Brad Fitzpatrick | 2008-11-29 | 24 | import urllib | |
| 04acea54 » | Brad Fitzpatrick | 2008-11-29 | 25 | ||
| 26 | # Core/AppEngine stuff | ||||
| 0ed12f30 » | Brad Fitzpatrick | 2008-11-28 | 27 | import wsgiref.handlers | |
| 28 | from google.appengine.api import users | ||||
| 29 | from google.appengine.ext import webapp | ||||
| 30 | from google.appengine.ext import db | ||||
| 04acea54 » | Brad Fitzpatrick | 2008-11-29 | 31 | from google.appengine.ext.webapp import template | |
| 0ed12f30 » | Brad Fitzpatrick | 2008-11-28 | 32 | from google.appengine.api import urlfetch | |
| 04acea54 » | Brad Fitzpatrick | 2008-11-29 | 33 | ||
| 34 | # Libraries included w/ app | ||||
| 0ed12f30 » | Brad Fitzpatrick | 2008-11-28 | 35 | import atom | |
| 36 | import atom.http_interface | ||||
| 37 | import atom.token_store | ||||
| 38 | import atom.url | ||||
| 04acea54 » | Brad Fitzpatrick | 2008-11-29 | 39 | import gdata.alt.appengine | |
| 40 | import gdata.auth | ||||
| 41 | import gdata.contacts.service as contactsservice | ||||
| 42 | import gdata.service | ||||
| 0ed12f30 » | Brad Fitzpatrick | 2008-11-28 | 43 | import simplejson | |
| 04acea54 » | Brad Fitzpatrick | 2008-11-29 | 44 | ||
| 45 | # App stuff | ||||
| 46 | import settings | ||||
| 47 | import models | ||||
| 48 | |||||
| 49 | VALID_HANDLE = re.compile(r"^\w+$") | ||||
| 50 | |||||
| 0ed12f30 » | Brad Fitzpatrick | 2008-11-28 | 51 | ||
| f2b7da9b » | Brad Fitzpatrick | 2008-11-29 | 52 | def NumberSuffixesMatch(num1, num2): | |
| 53 | """Given two phone numbers, return bool if they match. | ||||
| 54 | |||||
| 55 | Numbers are strings. A match is 7 matching final | ||||
| 56 | numbers, ignoring punctuations and space and stuff. | ||||
| 57 | """ | ||||
| 58 | num1 = re.sub(r"[^\d]", "", num1) | ||||
| 59 | num2 = re.sub(r"[^\d]", "", num2) | ||||
| 60 | if len(num1) < 6 or len(num2) < 6: | ||||
| 61 | return False | ||||
| 62 | return num1[-7:] == num2[-7:] | ||||
| 63 | |||||
| 64 | |||||
| 68e2b7b8 » | Brad Fitzpatrick | 2008-11-30 | 65 | def PhoneNumberListContainsNumber(number_list, number): | |
| 66 | """Searches number_list for number. Returns True if found. | ||||
| 67 | |||||
| 68 | Args: | ||||
| 69 | number_list: list of gdata PhoneNumber | ||||
| 70 | number: string phone number. | ||||
| 71 | |||||
| 72 | Returns: bool. | ||||
| 73 | """ | ||||
| 74 | for phone_number in number_list: | ||||
| 75 | google_number = phone_number.text | ||||
| 76 | if NumberSuffixesMatch(google_number, number): | ||||
| 77 | return True | ||||
| 78 | return False | ||||
| 79 | |||||
| 80 | |||||
| 81 | def GroupListContainsGroup(group_list, group): | ||||
| 82 | """Returns true if group found in group_list. | ||||
| 83 | |||||
| 84 | Args: | ||||
| 85 | group_list: array of GroupMembershipInfo | ||||
| 86 | group: a GroupMembershipInfo | ||||
| 87 | |||||
| 88 | Returns: bool. | ||||
| 89 | """ | ||||
| 90 | assert group | ||||
| 91 | |||||
| 92 | sought_href = group.href | ||||
| 93 | for potential_match in group_list: | ||||
| 94 | if potential_match.href == sought_href: | ||||
| 95 | return True | ||||
| 96 | return False | ||||
| 97 | |||||
| 98 | |||||
| f2b7da9b » | Brad Fitzpatrick | 2008-11-29 | 99 | def FindEntryToMergeInto(contact, feed): | |
| 100 | """Finds Entry (or None) in feed to merge contact into.""" | ||||
| 101 | contact_name = contact["name"] | ||||
| 102 | for entry in feed.entry: | ||||
| 103 | if entry.title and entry.title.text and \ | ||||
| 104 | entry.title.text == contact_name: | ||||
| 105 | return entry | ||||
| 106 | |||||
| 68e2b7b8 » | Brad Fitzpatrick | 2008-11-30 | 107 | for number_rec in contact["numbers"]: | |
| 108 | contact_number = number_rec["number"] | ||||
| 109 | if PhoneNumberListContainsNumber(entry.phone_number, | ||||
| 110 | contact_number): | ||||
| 111 | return entry | ||||
| f2b7da9b » | Brad Fitzpatrick | 2008-11-29 | 112 | ||
| 113 | return None | ||||
| 114 | |||||
| f3be64f3 » | Brad Fitzpatrick | 2008-11-29 | 115 | ||
| 15176620 » | Brad Fitzpatrick | 2008-11-29 | 116 | def PhoneRelType(text): | |
| 117 | """Given some free-formish text, map that to the GData phone rel.""" | ||||
| 118 | if re.match(r"^\s*(mobile|cell)", text, re.I): | ||||
| 119 | return "http://schemas.google.com/g/2005#mobile" | ||||
| 120 | if re.match(r"^\s*(work|office)", text, re.I): | ||||
| 121 | return "http://schemas.google.com/g/2005#work" | ||||
| 122 | if re.match(r"^\s*(house|home)", text, re.I): | ||||
| 123 | return "http://schemas.google.com/g/2005#home" | ||||
| 124 | return "http://schemas.google.com/g/2005#other" | ||||
| 125 | |||||
| 126 | |||||
| 71122ab1 » | Brad Fitzpatrick | 2008-11-30 | 127 | def VcardPhoneType(phone_rel): | |
| 128 | """Given a phone rel type from PhoneRelType, returns the vcard type.""" | ||||
| 129 | vcard_map = { | ||||
| 130 | "http://schemas.google.com/g/2005#mobile": "CELL", | ||||
| 131 | "http://schemas.google.com/g/2005#home": "HOME", | ||||
| 132 | "http://schemas.google.com/g/2005#work": "WORK", | ||||
| 133 | } | ||||
| 134 | if phone_rel in vcard_map: | ||||
| 135 | return vcard_map[phone_rel] | ||||
| 136 | return "OTHER" | ||||
| 137 | |||||
| 138 | |||||
| 15176620 » | Brad Fitzpatrick | 2008-11-29 | 139 | def NewContactEntry(contact, group=None): | |
| 68e2b7b8 » | Brad Fitzpatrick | 2008-11-30 | 140 | """Make a new GData Contact Entry from a submitted contact dict. | |
| 141 | |||||
| 142 | Returns: The new, populated cgdata.contacts.ContactEntry object. | ||||
| 143 | """ | ||||
| 15176620 » | Brad Fitzpatrick | 2008-11-29 | 144 | new_entry = gdata.contacts.ContactEntry() | |
| 68e2b7b8 » | Brad Fitzpatrick | 2008-11-30 | 145 | UpdateContactEntry(new_entry, contact, group) | |
| 146 | return new_entry | ||||
| 147 | |||||
| 148 | |||||
| 149 | def UpdateContactEntry(merge_entry, contact, group=None): | ||||
| 150 | """Merge user-submitted contact data into GData entry. | ||||
| 151 | |||||
| 152 | Args: | ||||
| 153 | merge_entry: The gdata.contacts.ContactEntry() object to | ||||
| 154 | merge into: | ||||
| 155 | contact: Input contact dictionary from user w/ fields | ||||
| 156 | group: optional GroupMembershipInfo to put user in | ||||
| 157 | |||||
| 158 | Returns: List of changes (terse English each). If no changes, | ||||
| 159 | returns the empty list, which is false in boolean context. | ||||
| 160 | """ | ||||
| 161 | changes = [] | ||||
| 162 | |||||
| 15176620 » | Brad Fitzpatrick | 2008-11-29 | 163 | if contact["name"]: | |
| 68e2b7b8 » | Brad Fitzpatrick | 2008-11-30 | 164 | # TODO: promote short names (e.g. "Brad" or "Brad F.") to | |
| 165 | # full names ("Brad Fitzpatrick"). For now, never modify | ||||
| 166 | # the name. | ||||
| 167 | if not merge_entry.title or not merge_entry.title.text: | ||||
| 168 | changes.append("set name: %s" % contact["name"]) | ||||
| 169 | merge_entry.title = atom.Title(text=contact["name"]) | ||||
| 15176620 » | Brad Fitzpatrick | 2008-11-29 | 170 | ||
| 171 | for number_rec in contact["numbers"]: | ||||
| 68e2b7b8 » | Brad Fitzpatrick | 2008-11-30 | 172 | if not PhoneNumberListContainsNumber(merge_entry.phone_number, | |
| 173 | number_rec["number"]): | ||||
| 174 | changes.append("adding number: %s" % number_rec["number"]) | ||||
| 175 | merge_entry.phone_number.append(gdata.contacts.PhoneNumber( | ||||
| 176 | rel=PhoneRelType(number_rec["type"]), | ||||
| 177 | text=number_rec["number"])) | ||||
| 15176620 » | Brad Fitzpatrick | 2008-11-29 | 178 | ||
| 68e2b7b8 » | Brad Fitzpatrick | 2008-11-30 | 179 | if group and not GroupListContainsGroup(merge_entry.group_membership_info, | |
| 180 | group): | ||||
| 181 | changes.append("adding to group.") | ||||
| 182 | merge_entry.group_membership_info.append(group) | ||||
| 15176620 » | Brad Fitzpatrick | 2008-11-29 | 183 | ||
| 68e2b7b8 » | Brad Fitzpatrick | 2008-11-30 | 184 | return changes | |
| 185 | |||||
| 15176620 » | Brad Fitzpatrick | 2008-11-29 | 186 | ||
| f3be64f3 » | Brad Fitzpatrick | 2008-11-29 | 187 | class Updater(object): | |
| 188 | """Queues up updates and flushes them to gdata batch as needed.""" | ||||
| 189 | |||||
| 9dbc186f » | Brad Fitzpatrick | 2008-11-30 | 190 | def __init__(self, client=None, noop_mode=False): | |
| f3be64f3 » | Brad Fitzpatrick | 2008-11-29 | 191 | self.client = client | |
| 192 | self.batch_feed = gdata.contacts.ContactsFeed() | ||||
| 9dbc186f » | Brad Fitzpatrick | 2008-11-30 | 193 | self.noop_mode = noop_mode | |
| f3be64f3 » | Brad Fitzpatrick | 2008-11-29 | 194 | ||
| 195 | def AddInsert(self, entry): | ||||
| 9dbc186f » | Brad Fitzpatrick | 2008-11-30 | 196 | if self.noop_mode: | |
| 197 | return | ||||
| f3be64f3 » | Brad Fitzpatrick | 2008-11-29 | 198 | self.batch_feed.AddInsert(entry) | |
| 199 | self.FlushIfNeeded() | ||||
| 200 | |||||
| 201 | def AddUpdate(self, entry): | ||||
| 9dbc186f » | Brad Fitzpatrick | 2008-11-30 | 202 | if self.noop_mode: | |
| 203 | return | ||||
| f3be64f3 » | Brad Fitzpatrick | 2008-11-29 | 204 | self.batch_feed.AddUpdate(entry) | |
| 205 | self.FlushIfNeeded() | ||||
| 206 | |||||
| 207 | def FlushIfNeeded(self): | ||||
| 58351427 » | Brad Fitzpatrick | 2008-12-01 | 208 | if len(self.batch_feed.entry) >= 15: # 100 is max but too slow for App Engine | |
| f3be64f3 » | Brad Fitzpatrick | 2008-11-29 | 209 | self.Flush() | |
| 210 | |||||
| 211 | def Flush(self): | ||||
| 212 | if not len(self.batch_feed.entry): | ||||
| 213 | return | ||||
| 214 | self.client.ExecuteBatch(self.batch_feed, | ||||
| 215 | gdata.contacts.service.DEFAULT_BATCH_URL) | ||||
| 216 | self.batch_feed = gdata.contacts.ContactsFeed() | ||||
| 217 | |||||
| bfbfcd50 » | Brad Fitzpatrick | 2008-12-01 | 218 | def FlushBufferEmpty(self): | |
| 219 | return len(self.batch_feed.entry) == 0 | ||||
| 220 | |||||
| f3be64f3 » | Brad Fitzpatrick | 2008-11-29 | 221 | ||
| 6b65d890 » | Brad Fitzpatrick | 2008-11-30 | 222 | class AddressBookerBaseHandler(webapp.RequestHandler): | |
| 223 | def WritePage(self, title, template_file, dict=None): | ||||
| 224 | sign_inout = "" | ||||
| 225 | user = users.get_current_user() | ||||
| 226 | if user: | ||||
| 227 | sign_inout = 'Hello, %s! <a href="%s">Sign Out</a>' % ( | ||||
| 228 | cgi.escape(user.nickname()), | ||||
| 229 | users.create_logout_url('http://%s/' % settings.HOST_NAME)) | ||||
| 230 | else: | ||||
| 231 | sign_inout = '<a href="%s">Sign In</a>' % ( | ||||
| 232 | users.create_login_url('http://%s/' % settings.HOST_NAME)) | ||||
| 233 | |||||
| 234 | self.response.out.write(template.render('page.html', { | ||||
| 235 | 'title': title or "AddressBooker", | ||||
| 236 | 'sign_inout': sign_inout, | ||||
| 237 | 'body': template.render(template_file, dict or {}), | ||||
| 238 | })) | ||||
| 239 | |||||
| 240 | class AddressBooker(AddressBookerBaseHandler): | ||||
| 0ed12f30 » | Brad Fitzpatrick | 2008-11-28 | 241 | ||
| 242 | def get(self): | ||||
| 243 | self.response.headers['Content-Type'] = 'text/html' | ||||
| 6b65d890 » | Brad Fitzpatrick | 2008-11-30 | 244 | self.WritePage("AddressBooker", "index.html") | |
| 245 | |||||
| 0ed12f30 » | Brad Fitzpatrick | 2008-11-28 | 246 | ||
| 6b65d890 » | Brad Fitzpatrick | 2008-11-30 | 247 | class Submit(webapp.RequestHandler): | |
| 248 | |||||
| 249 | def get(self): | ||||
| 250 | self.redirect("/") | ||||
| 0ed12f30 » | Brad Fitzpatrick | 2008-11-28 | 251 | ||
| 252 | def post(self): | ||||
| 4ee0964e » | Brad Fitzpatrick | 2008-11-28 | 253 | handle = self.request.get('handle') | |
| 04acea54 » | Brad Fitzpatrick | 2008-11-29 | 254 | if not handle: | |
| 255 | raise "Missing argument 'handle'" | ||||
| 256 | if not VALID_HANDLE.match(handle): | ||||
| 257 | raise "Bogus handle." | ||||
| 258 | |||||
| 4ee0964e » | Brad Fitzpatrick | 2008-11-28 | 259 | json = self.request.get('json') | |
| 260 | group = self.request.get('group') | ||||
| 261 | |||||
| 6b65d890 » | Brad Fitzpatrick | 2008-11-30 | 262 | post_dump = models.PostDump(key_name="handle:" + handle, | |
| 263 | json=json, | ||||
| 264 | group=group, | ||||
| 265 | handle=handle) | ||||
| 266 | post_dump.put() | ||||
| 04acea54 » | Brad Fitzpatrick | 2008-11-29 | 267 | ||
| 6b65d890 » | Brad Fitzpatrick | 2008-11-30 | 268 | user = users.get_current_user() | |
| 269 | target_url = "http://%s/menu?key=%s" % ( | ||||
| 270 | settings.HOST_NAME, str(post_dump.key())) | ||||
| 271 | if user: | ||||
| 272 | self.redirect(target_url) | ||||
| 273 | else: | ||||
| 274 | self.redirect(users.create_login_url(target_url)) | ||||
| 04acea54 » | Brad Fitzpatrick | 2008-11-29 | 275 | ||
| 276 | |||||
| 6b65d890 » | Brad Fitzpatrick | 2008-11-30 | 277 | class Menu(AddressBookerBaseHandler): | |
| 278 | def get(self): | ||||
| 279 | key = self.request.get('key') | ||||
| 280 | if not key: | ||||
| 281 | raise "Missing argument 'key'" | ||||
| 282 | post_dump = models.PostDump.get(db.Key(key)) | ||||
| 283 | if not post_dump: | ||||
| 284 | raise "State lost? Um, do it again." | ||||
| 285 | |||||
| 286 | contacts = simplejson.loads(post_dump.json) | ||||
| 287 | |||||
| 288 | self.WritePage("AddressBooker Menu", "menu.html", { | ||||
| 289 | 'n_contacts': len(contacts), | ||||
| 290 | 'key': str(post_dump.key()), | ||||
| 291 | }) | ||||
| 292 | |||||
| 293 | |||||
| 04acea54 » | Brad Fitzpatrick | 2008-11-29 | 294 | class MergeView(webapp.RequestHandler): | |
| 295 | """View the contacts for a given handle.""" | ||||
| 296 | |||||
| 297 | def get(self): | ||||
| 298 | key = self.request.get('key') | ||||
| 299 | if not key: | ||||
| 300 | raise "Missing argument 'key'" | ||||
| 301 | post_dump = models.PostDump.get(db.Key(key)) | ||||
| 302 | if not post_dump: | ||||
| 303 | raise "State lost? Um, do it again." | ||||
| 304 | |||||
| 305 | contacts = simplejson.loads(post_dump.json) | ||||
| 0ed12f30 » | Brad Fitzpatrick | 2008-11-28 | 306 | for contact in contacts: | |
| 307 | self.response.out.write("<br clear='both'><h2>%s</h2>" % contact["name"]) | ||||
| 308 | self.response.out.write("<img src='%s' style='float:left' />" % contact["img"]) | ||||
| 309 | for number in contact["numbers"]: | ||||
| 6b65d890 » | Brad Fitzpatrick | 2008-11-30 | 310 | # obf_number = re.sub(r"\d{3}$", "<i>xxx</i>", number["number"]) | |
| 0ed12f30 » | Brad Fitzpatrick | 2008-11-28 | 311 | self.response.out.write("<p><b>%s</b> %s</p>" % ( | |
| 6b65d890 » | Brad Fitzpatrick | 2008-11-30 | 312 | cgi.escape(number["type"]), cgi.escape(number["number"]))) | |
| 04acea54 » | Brad Fitzpatrick | 2008-11-29 | 313 | ||
| 314 | |||||
| 71122ab1 » | Brad Fitzpatrick | 2008-11-30 | 315 | class VCard(webapp.RequestHandler): | |
| 316 | """Get a vcard.""" | ||||
| 317 | |||||
| 318 | def get(self): | ||||
| 319 | key = self.request.get('key') | ||||
| 320 | if not key: | ||||
| 321 | raise "Missing argument 'key'" | ||||
| 322 | post_dump = models.PostDump.get(db.Key(key)) | ||||
| 323 | if not post_dump: | ||||
| 324 | raise "State lost? Um, do it again." | ||||
| 325 | |||||
| 326 | contacts = simplejson.loads(post_dump.json) | ||||
| 327 | self.response.headers['Content-Type'] = "text/x-vcard; charset=UTF-8" | ||||
| 328 | self.response.headers['Content-Disposition'] = "attachment; filename=\"addressbooker.vcf\"" | ||||
| 329 | |||||
| 330 | for contact in contacts: | ||||
| 331 | self.response.out.write("BEGIN:VCARD\n") | ||||
| 332 | self.response.out.write("VERSION:3.0\n") | ||||
| 333 | self.response.out.write("FN:%s\n" % (contact["name"] or "")) | ||||
| 334 | for number in contact["numbers"]: | ||||
| 335 | self.response.out.write("TEL;type=%s:%s\n" % ( | ||||
| 336 | VcardPhoneType(PhoneRelType(number["type"])), | ||||
| 337 | number["number"])) | ||||
| 338 | self.response.out.write("END:VCARD\n") | ||||
| 339 | |||||
| 340 | |||||
| 6b65d890 » | Brad Fitzpatrick | 2008-11-30 | 341 | class MergeGoogle(AddressBookerBaseHandler): | |
| 04acea54 » | Brad Fitzpatrick | 2008-11-29 | 342 | """Merge contacts into Google Contacts w/ Google Contacts API.""" | |
| 343 | |||||
| 344 | def get(self): | ||||
| 9dbc186f » | Brad Fitzpatrick | 2008-11-30 | 345 | self.ProcessMerge(method='GET') | |
| 346 | |||||
| 347 | def post(self): | ||||
| 348 | self.ProcessMerge(method='POST') | ||||
| 349 | |||||
| 350 | def ProcessMerge(self, method): | ||||
| 04acea54 » | Brad Fitzpatrick | 2008-11-29 | 351 | key = self.request.get('key') | |
| 352 | if not key: | ||||
| 353 | raise "Missing argument 'key'" | ||||
| 354 | post_dump = models.PostDump.get(db.Key(key)) | ||||
| 355 | if not post_dump: | ||||
| 356 | raise "State lost? Um, do it again." | ||||
| 357 | |||||
| 6b65d890 » | Brad Fitzpatrick | 2008-11-30 | 358 | body = [] # of str | |
| f2b7da9b » | Brad Fitzpatrick | 2008-11-29 | 359 | def out(str): | |
| 6b65d890 » | Brad Fitzpatrick | 2008-11-30 | 360 | body.append(str) | |
| 04acea54 » | Brad Fitzpatrick | 2008-11-29 | 361 | ||
| 362 | # We need a logged-in user for the GData.client.token_store to work. | ||||
| 6b65d890 » | Brad Fitzpatrick | 2008-11-30 | 363 | user = users.get_current_user() | |
| 04acea54 » | Brad Fitzpatrick | 2008-11-29 | 364 | if not user: | |
| 365 | logging.info("Redirecting to sign-in."); | ||||
| 6b65d890 » | Brad Fitzpatrick | 2008-11-30 | 366 | sign_in_url = users.create_login_url('http://%s/gcontacts?key=%s' % | |
| 04acea54 » | Brad Fitzpatrick | 2008-11-29 | 367 | (settings.HOST_NAME, key)) | |
| 368 | self.redirect(sign_in_url) | ||||
| 369 | return | ||||
| 370 | |||||
| 371 | # And the subclass of the Service for the Contacts API: | ||||
| 372 | client = contactsservice.ContactsService() | ||||
| 373 | gdata.alt.appengine.run_on_appengine(client) | ||||
| 374 | |||||
| 375 | contacts = simplejson.loads(post_dump.json) | ||||
| 376 | |||||
| 377 | contacts_url = "http://www.google.com/m8/feeds/contacts/default/full" | ||||
| 378 | auth_base_url = "http://www.google.com/m8/feeds/" | ||||
| 379 | |||||
| 380 | session_token = client.token_store.find_token(auth_base_url) | ||||
| 381 | if type(session_token) == atom.http_interface.GenericToken: | ||||
| 382 | session_token = None | ||||
| 383 | |||||
| 384 | if not session_token: | ||||
| 385 | # Find the AuthSub token and upgrade it to a session token. | ||||
| 386 | auth_token = gdata.auth.extract_auth_sub_token_from_url(self.request.uri) | ||||
| 387 | if auth_token: | ||||
| 388 | session_token = client.upgrade_to_session_token(auth_token) | ||||
| 389 | client.token_store.add_token(session_token) | ||||
| 390 | # just to sanitize our URL: | ||||
| 6b65d890 » | Brad Fitzpatrick | 2008-11-30 | 391 | self.redirect('http://%s/gcontacts?key=%s' % | |
| 04acea54 » | Brad Fitzpatrick | 2008-11-29 | 392 | (settings.HOST_NAME, key)) | |
| 393 | else: | ||||
| 394 | next = self.request.uri | ||||
| 395 | auth_sub_url = client.GenerateAuthSubURL(next, auth_base_url, | ||||
| 396 | secure=False, session=True) | ||||
| 397 | self.redirect(str(auth_sub_url)) | ||||
| 398 | return | ||||
| bfbfcd50 » | Brad Fitzpatrick | 2008-12-01 | 399 | ||
| 400 | # Are we a GET request in auto-submit mode? | ||||
| 401 | working = (method == "GET" and self.request.get("continue")) | ||||
| 04acea54 » | Brad Fitzpatrick | 2008-11-29 | 402 | ||
| 15176620 » | Brad Fitzpatrick | 2008-11-29 | 403 | # Process Groups | |
| 04acea54 » | Brad Fitzpatrick | 2008-11-29 | 404 | groups_feed = client.Get("http://www.google.com/m8/feeds/groups/default/full") | |
| 9c78ed16 » | Brad Fitzpatrick | 2008-11-30 | 405 | groups_feed = gdata.contacts.GroupsFeedFromString(groups_feed.ToString().decode("utf-8")) | |
| f2b7da9b » | Brad Fitzpatrick | 2008-11-29 | 406 | group_name = {} # id -> name | |
| 407 | group_id = {} # name -> id | ||||
| 408 | for group in groups_feed.entry: | ||||
| 15176620 » | Brad Fitzpatrick | 2008-11-29 | 409 | group_name[group.id.text] = group.content.text | |
| 410 | group_id[group.content.text] = group.id.text | ||||
| 9c78ed16 » | Brad Fitzpatrick | 2008-11-30 | 411 | if False: | |
| 15176620 » | Brad Fitzpatrick | 2008-11-29 | 412 | out("<h3>Group</h3><ul>") | |
| 413 | out("<li>id: %s</li>" % cgi.escape(group.id.text)) | ||||
| 9c78ed16 » | Brad Fitzpatrick | 2008-11-30 | 414 | out("<li>content: %s</li>" % cgi.escape(group.content.text.decode("utf-8"))) | |
| 15176620 » | Brad Fitzpatrick | 2008-11-29 | 415 | out("</ul>") | |
| 416 | |||||
| 6b65d890 » | Brad Fitzpatrick | 2008-11-30 | 417 | # Initialize 'group' (or keep it None, if not using groups), creating the | |
| 418 | # group if necessary. | ||||
| 419 | group = None | ||||
| 420 | dest_group_name = post_dump.group | ||||
| 421 | if dest_group_name and dest_group_name not in group_id: | ||||
| 15176620 » | Brad Fitzpatrick | 2008-11-29 | 422 | new_group = gdata.contacts.GroupEntry(title=atom.Title( | |
| 423 | text=dest_group_name)) | ||||
| 424 | group = client.CreateGroup(new_group) | ||||
| 425 | group_name[group.id] = dest_group_name | ||||
| 426 | group_id[dest_group_name] = group.id | ||||
| 6b65d890 » | Brad Fitzpatrick | 2008-11-30 | 427 | if dest_group_name: | |
| 428 | group = gdata.contacts.GroupMembershipInfo(href=unicode(group_id[dest_group_name])) | ||||
| 04acea54 » | Brad Fitzpatrick | 2008-11-29 | 429 | ||
| 430 | full_feed_url = contacts_url + "?max-results=99999" | ||||
| 431 | feed = client.Get(full_feed_url, converter=gdata.contacts.ContactsFeedFromString) | ||||
| f2b7da9b » | Brad Fitzpatrick | 2008-11-29 | 432 | ||
| 9dbc186f » | Brad Fitzpatrick | 2008-11-30 | 433 | preview_mode = True | |
| 434 | title = "Preview Proposed GContacts Changes" | ||||
| 435 | if method == "POST": | ||||
| 436 | preview_mode = False | ||||
| 437 | title = "GContacts Updates Complete" | ||||
| 438 | |||||
| 439 | contact_changes = [] | ||||
| 440 | |||||
| 441 | updater = Updater(client=client, noop_mode=preview_mode); | ||||
| f3be64f3 » | Brad Fitzpatrick | 2008-11-29 | 442 | ||
| 4c47dff7 » | Brad Fitzpatrick | 2008-11-30 | 443 | no_change_contacts = [] | |
| f2b7da9b » | Brad Fitzpatrick | 2008-11-29 | 444 | for contact in contacts: | |
| 9dbc186f » | Brad Fitzpatrick | 2008-11-30 | 445 | contact_change = { | |
| 446 | "contact": contact, | ||||
| 447 | } | ||||
| 448 | |||||
| f2b7da9b » | Brad Fitzpatrick | 2008-11-29 | 449 | merge_entry = FindEntryToMergeInto(contact, feed) | |
| 450 | if merge_entry: | ||||
| 9dbc186f » | Brad Fitzpatrick | 2008-11-30 | 451 | entry_changes = UpdateContactEntry(merge_entry, contact, group=group) | |
| 452 | if entry_changes: | ||||
| 453 | contact_change["action"] = "merge" | ||||
| 454 | contact_change["merge_target"] = merge_entry.title.text.decode("utf-8") | ||||
| 455 | contact_change["changes"] = entry_changes | ||||
| 68e2b7b8 » | Brad Fitzpatrick | 2008-11-30 | 456 | updater.AddUpdate(merge_entry) | |
| 457 | else: | ||||
| 9dbc186f » | Brad Fitzpatrick | 2008-11-30 | 458 | contact_change["action"] = "none" | |
| f2b7da9b » | Brad Fitzpatrick | 2008-11-29 | 459 | else: | |
| 9dbc186f » | Brad Fitzpatrick | 2008-11-30 | 460 | contact_change["action"] = "new" | |
| 461 | contact_change["changes"] = ["Create new contact."] | ||||
| 15176620 » | Brad Fitzpatrick | 2008-11-29 | 462 | updater.AddInsert(NewContactEntry(contact, group=group)) | |
| 463 | |||||
| 4c47dff7 » | Brad Fitzpatrick | 2008-11-30 | 464 | if contact_change["action"] == "none": | |
| 465 | no_change_contacts.append(contact_change) | ||||
| 466 | else: | ||||
| 467 | contact_changes.append(contact_change) | ||||
| bfbfcd50 » | Brad Fitzpatrick | 2008-12-01 | 468 | if not preview_mode and updater.FlushBufferEmpty(): | |
| 469 | # redirect for the next batch; new HTTP request | ||||
| 470 | # to get around App Engine long request deadlines. | ||||
| 471 | self.redirect('http://%s/gcontacts?key=%s&continue=1' % | ||||
| 472 | (settings.HOST_NAME, key)) | ||||
| 58351427 » | Brad Fitzpatrick | 2008-12-01 | 473 | return | |
| bfbfcd50 » | Brad Fitzpatrick | 2008-12-01 | 474 | ||
| 4c47dff7 » | Brad Fitzpatrick | 2008-11-30 | 475 | ||
| 476 | # Put the boring ones at bottom. | ||||
| bfbfcd50 » | Brad Fitzpatrick | 2008-12-01 | 477 | n_changes = len(contact_changes) | |
| 4c47dff7 » | Brad Fitzpatrick | 2008-11-30 | 478 | contact_changes.extend(no_change_contacts) | |
| 479 | |||||
| 15176620 » | Brad Fitzpatrick | 2008-11-29 | 480 | render_google_list = False | |
| 481 | if render_google_list: | ||||
| 482 | out("<hr />") | ||||
| 483 | for entry in feed.entry: | ||||
| 484 | if entry.title and entry.title.text: | ||||
| 485 | out('<h3>Entry Title: %s</h3>' % ( | ||||
| 486 | entry.title.text.decode('UTF-8'))) | ||||
| 487 | else: | ||||
| 488 | out("<h3>(title-less entry)</h3>"); | ||||
| 04acea54 » | Brad Fitzpatrick | 2008-11-29 | 489 | ||
| 15176620 » | Brad Fitzpatrick | 2008-11-29 | 490 | for phone_number in entry.phone_number: | |
| 491 | out("<p><b>Phone: (%s)</b> %s</p>" % | ||||
| 04acea54 » | Brad Fitzpatrick | 2008-11-29 | 492 | (phone_number.rel, phone_number.text)) | |
| 493 | |||||
| 15176620 » | Brad Fitzpatrick | 2008-11-29 | 494 | for email in entry.email: | |
| 495 | out("<p><b>Email: (%s)</b> %s</p>" % | ||||
| 04acea54 » | Brad Fitzpatrick | 2008-11-29 | 496 | (email.rel, email.address)) | |
| 497 | |||||
| 15176620 » | Brad Fitzpatrick | 2008-11-29 | 498 | for group in entry.group_membership_info: | |
| 499 | out("<p><b>Group: (%s)</b> %s</p>" % | ||||
| 500 | (group.href, cgi.escape(str(group)))) | ||||
| 501 | |||||
| 502 | updater.Flush() | ||||
| 04acea54 » | Brad Fitzpatrick | 2008-11-29 | 503 | ||
| 6b65d890 » | Brad Fitzpatrick | 2008-11-30 | 504 | self.WritePage(title, "google-merge.html", { | |
| 9dbc186f » | Brad Fitzpatrick | 2008-11-30 | 505 | "preview_mode": preview_mode, | |
| 6b65d890 » | Brad Fitzpatrick | 2008-11-30 | 506 | "body": "".join(body), | |
| 9dbc186f » | Brad Fitzpatrick | 2008-11-30 | 507 | "session_token": str(session_token), | |
| 4c47dff7 » | Brad Fitzpatrick | 2008-11-30 | 508 | "key": key, | |
| bfbfcd50 » | Brad Fitzpatrick | 2008-12-01 | 509 | "changes": contact_changes, | |
| 510 | "n_changes": n_changes, | ||||
| 511 | "working": working, | ||||
| 6b65d890 » | Brad Fitzpatrick | 2008-11-30 | 512 | }) | |
| 04acea54 » | Brad Fitzpatrick | 2008-11-29 | 513 | ||
| 0ed12f30 » | Brad Fitzpatrick | 2008-11-28 | 514 | ||
| 515 | class Acker(webapp.RequestHandler): | ||||
| 516 | """Simulates an HTML page to prove ownership of this domain for AuthSub | ||||
| 517 | registration.""" | ||||
| 518 | |||||
| 519 | def get(self): | ||||
| 520 | self.response.headers['Content-Type'] = 'text/plain' | ||||
| 521 | self.response.out.write('This file present for AuthSub registration.') | ||||
| 522 | |||||
| 523 | |||||
| 524 | def main(): | ||||
| 04acea54 » | Brad Fitzpatrick | 2008-11-29 | 525 | application = webapp.WSGIApplication([ | |
| 6b65d890 » | Brad Fitzpatrick | 2008-11-30 | 526 | ('/', AddressBooker), | |
| 527 | ('/submit', Submit), | ||||
| 528 | ('/menu', Menu), | ||||
| 71122ab1 » | Brad Fitzpatrick | 2008-11-30 | 529 | ('/vcard', VCard), | |
| 6b65d890 » | Brad Fitzpatrick | 2008-11-30 | 530 | ('/gcontacts', MergeGoogle), | |
| 531 | ('/view', MergeView), | ||||
| 04acea54 » | Brad Fitzpatrick | 2008-11-29 | 532 | ('/google72db3d6838b4c438.html', Acker), | |
| 533 | ], debug=True) | ||||
| 0ed12f30 » | Brad Fitzpatrick | 2008-11-28 | 534 | wsgiref.handlers.CGIHandler().run(application) | |
| 535 | |||||
| 536 | |||||
| 537 | if __name__ == '__main__': | ||||
| 538 | main() | ||||







