Google Contacts to Asterisk Phonebook — OAuth 2.0 Fix

Google deprecated legacy access to their Contacts API, so I updated to use OAuth 2.0.

UPDATE: Now on GitHub!

#!/usr/bin/env python
# Original By: John Baab
# Email:
# Updated By: Jay Schulman
# Email:
# Updated Again By: Philip Rosenberg-Watt
# Purpose: syncs contacts from google to asterisk server
# Updates: Updating for Google API v3 with support for Google Apps using OAuth2
# Requirements: python, gdata python client, asterisk
# License:
# This Package is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public
# License as published by the Free Software Foundation; either
# version 3 of the License, or (at your option) any later version.
# This package is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# General Public License for more details.
# You should have received a copy of the GNU General Public
# License along with this package; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
# On Debian & Ubuntu systems, a complete copy of the GPL can be found under
# /usr/share/common-licenses/GPL-3, or (at your option) any later version
import atom,re,sys,os
import json
import gdata.auth
import gdata.contacts
import gdata.contacts.client
import argparse
from oauth2client import client
from oauth2client import file
from oauth2client import tools

# Native application Client ID JSON from the Google Developers Console,
# store in the same directory as this script:

parent_parsers = [tools.argparser]
parser = argparse.ArgumentParser(parents=parent_parsers)
parser.add_argument("--allgroups", help="only works in combination with --group to show members with multiple groups", action="store_true", default=False)
parser.add_argument("--anygroup", help="show members of any user-created group (not My Contacts), OVERRIDES other options", action="store_true", default=False)
parser.add_argument("--asterisk", help="send commands to Asterisk instead of printing to console", action='store_true', default=False)
parser.add_argument("--dbname", help="database tree to use")
parser.add_argument("--delete", help="delete the existing database first", action='store_true', default=False)
parser.add_argument('--group', action="append", help='group name, can use multiple times')
args = parser.parse_args()

if args.dbname is None:
    args.dbname = "cidname"

phone_map = {2: ["A", "B", "C"],
             3: ["D", "E", "F"],
             4: ["G", "H", "I"],
             5: ["J", "K", "L"],
             6: ["M", "N", "O"],
             7: ["P", "Q", "R", "S"],
             8: ["T", "U", "V"],
             9: ["W", "X", "Y", "Z"]}

phone_map_one2one = {}
for k, v in phone_map.items():
    for l in v:
        phone_map_one2one[l] = str(k)
def phone_translate(phone_number):
    new_number = ""
    for l in phone_number:    
        if l.upper() in phone_map_one2one.keys():
            new_number += phone_map_one2one[l.upper()]
            l = re.sub('[^0-9]', '', l)
            new_number += l
    if len(new_number) == 11 and new_number[0] == "1":
        new_number = new_number[1:]
    return new_number

def get_auth_token():
    scope = ''
    user_agent = __name__
    client_secrets = os.path.join(os.path.dirname(__file__), CLIENT_SECRETS_JSON)
    filename = os.path.splitext(__file__)[0] + '.dat'

    flow = client.flow_from_clientsecrets(client_secrets, scope=scope, message=tools.message_if_missing(client_secrets))
    storage = file.Storage(filename)
    credentials = storage.get()
    if credentials is None or credentials.invalid:
        credentials = tools.run_flow(flow, storage, args)

    j = json.loads(open(filename).read())

    return gdata.gauth.OAuth2Token(j['client_id'], j['client_secret'], scope, user_agent, access_token = j['access_token'], refresh_token = j['refresh_token'])

def add_to_asterisk(dbname, cid, name):
    command = "asterisk -rx 'database put " + dbname + " " + cid + " "" + name + ""'"
    if args.asterisk:
        print command.encode('utf8')

def main():
    # Change this if you aren't in the US.  If you have more than one country code in your contacts,
    # then use an empty string and make sure that each number has a country code.
    country_code = ""
    token = get_auth_token()
    gd_client = gdata.contacts.client.ContactsClient()
    gd_client = token.authorize(gd_client)
    qry = gdata.contacts.client.ContactsQuery(max_results=2000)
    feed = gd_client.GetContacts(query=qry)
    groups = {}
    gq = gd_client.GetGroups()
    for e in gq.entry:
        striptext = "System Group: "
        groupname = e.title.text
        if groupname.startswith(striptext):
            groupname = groupname[len(striptext):]
        groups[] = groupname
    # delete all of our contacts before we refetch them, this will allow deletions
    if args.delete:
        command = "asterisk -rx 'database deltree %s'" % args.dbname
        if args.asterisk:
            print command

    # for each phone number in the contacts
    for i, entry in enumerate(feed.entry):
        glist = []
        for grp in entry.group_membership_info:

        name = None
        if entry.organization is not None and is not None:
            name =
        if entry.title.text is not None:
            name = entry.title.text
        if entry.nickname is not None:
            name = entry.nickname.text
        for r in entry.relation:
            if r.label == "CID":
                name = r.text

        for phone in entry.phone_number:
            # Strip out any non numeric characters and convert to UTF-8
#             phone.text = re.sub('[^0-9]', '', phone.text)
            phone.text = unicode(phone.text, 'utf8')
            phone.text = phone_translate(phone.text)
            # Remove leading digit if it exists, we will add this again later for all numbers
            # Only if a country code is defined.
            if country_code != "":
                phone.text = re.compile('^+?%s' % country_code, '', phone.text)
            phone.text = country_code + phone.text

            name = name.replace(''','')
            name = name.replace('"','')

            if args.anygroup:
                if (("My Contacts" in glist and len(glist) > 1) or
                    ("My Contacts" not in glist and len(glist) > 0)):
                    add_to_asterisk(args.dbname, phone.text, name)
                    if args.allgroups:
                        if set(
                            add_to_asterisk(args.dbname, phone.text, name)
                        for g in
                            if g in glist:
                                add_to_asterisk(args.dbname, phone.text, name)
                    add_to_asterisk(args.dbname, phone.text, name)

if __name__ == '__main__':

Author: PhilRW

software engineer, pianist, polymath

3 thoughts on “Google Contacts to Asterisk Phonebook — OAuth 2.0 Fix”

  1. Dear Phil. I am running an asterisk 11 in a debian server for my SoHo needs. I was looking for a way to import my contacts from google and i came across this site. could you explain a bit more on how to make it work, please?
    My incoming dialplan from extensions.conf is:
    exten => 22120214087,1,Set(CALLERID(name)=${DB(cidname/${CALLERID(num)})})
    exten => 22120214087,n,Answer()
    exten => 22120214087,n,Wait(1)
    exten => 22120214087,n,Dial(SIP/450&SIP/451,15)

    At the moment i am using a manual entry of family in astdb and it is retrieved from the CALLERID(name)…
    What do we have to do to our configs to make google contacts sync to astdb every 6 hours or so?


Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s