├── .gitignore ├── COPYING.MIT ├── README.md ├── index.js ├── package.json └── tests └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | 10 | pids 11 | logs 12 | results 13 | 14 | node_modules 15 | npm-debug.log 16 | .idea -------------------------------------------------------------------------------- /COPYING.MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 Lajos Koszti http://ajnasz.hu 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Node.js wrapper for the Google Contacts API. 2 | 3 | # Install 4 | 5 | ``` 6 | npm install google-contacts 7 | ``` 8 | 9 | # Usage 10 | 11 | ```javascript 12 | var GoogleContacts = require('google-contacts').GoogleContacts; 13 | var c = new GoogleContacts({ 14 | token: 'oauth2 token...' 15 | }); 16 | 17 | c.getContacts(cb, params); 18 | 19 | ``` 20 | 21 | Params: 22 | 23 | **type** (default: 'contacts') 24 | **alt** (default: json) 25 | **projection** 26 | **email** (default: 'default') 27 | **max-results** (default: 2000) 28 | 29 | See [https://developers.google.com/google-apps/contacts/v3/](https://developers.google.com/google-apps/contacts/v3/). 30 | 31 | # Test 32 | 33 | ``` 34 | GOOGLE_TOKEN=sometoken npm run test 35 | # verbose test 36 | DEBUG=google-contacts GOOGLE_TOKEN=sometoken npm run test 37 | ``` 38 | 39 | You can get a test token at [https://developers.google.com/oauthplayground/](https://developers.google.com/oauthplayground/). 40 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @todo: recursively send requests until all contacts are fetched 3 | * 4 | * @see https://developers.google.com/google-apps/contacts/v3/reference#ContactsFeed 5 | * 6 | * To API test requests: 7 | * 8 | * @see https://developers.google.com/oauthplayground/ 9 | * 10 | * To format JSON nicely: 11 | * 12 | * @see http://jsonviewer.stack.hu/ 13 | * 14 | * Note: The Contacts API has a hard limit to the number of results it can return at a 15 | * time even if you explicitly request all possible results. If the requested feed has 16 | * more fields than can be returned in a single response, the API truncates the feed and adds 17 | * a "Next" link that allows you to request the rest of the response. 18 | */ 19 | var EventEmitter = require('events').EventEmitter, 20 | _ = require('lodash'), 21 | qs = require('querystring'), 22 | util = require('util'), 23 | url = require('url'), 24 | https = require('https'), 25 | debug = require('debug')('google-contacts'); 26 | 27 | var GoogleContacts = function (params) { 28 | if (typeof params === 'string') { 29 | params = {token: params} 30 | } 31 | if (!params) { 32 | params = {}; 33 | } 34 | 35 | this.contacts = []; 36 | this.consumerKey = params.consumerKey ? params.consumerKey : null; 37 | this.consumerSecret = params.consumerSecret ? params.consumerSecret : null; 38 | this.token = params.token ? params.token : null; 39 | this.refreshToken = params.refreshToken ? params.refreshToken : null; 40 | 41 | this.params = _.defaults(params, {thin: true}); 42 | }; 43 | 44 | GoogleContacts.prototype = {}; 45 | 46 | util.inherits(GoogleContacts, EventEmitter); 47 | 48 | GoogleContacts.prototype._get = function (params, cb) { 49 | if (typeof params === 'function') { 50 | cb = params; 51 | params = {}; 52 | } 53 | 54 | var req = { 55 | host: 'www.google.com', 56 | port: 443, 57 | path: this._buildPath(params), 58 | method: 'GET', 59 | headers: { 60 | 'Authorization': 'OAuth ' + this.token, 61 | 'GData-Version': 3 62 | } 63 | }; 64 | 65 | debug(req); 66 | 67 | https.request(req, function (res) { 68 | var data = ''; 69 | 70 | res.on('data', function (chunk) { 71 | debug('got ' + chunk.length + ' bytes'); 72 | data += chunk.toString('utf-8'); 73 | }); 74 | 75 | res.on('error', function (err) { 76 | cb(err); 77 | }); 78 | 79 | res.on('end', function () { 80 | if (res.statusCode < 200 || res.statusCode >= 300) { 81 | var error = new Error('Bad client request status: ' + res.statusCode); 82 | return cb(error); 83 | } 84 | try { 85 | debug(data); 86 | cb(null, JSON.parse(data)); 87 | } 88 | catch (err) { 89 | cb(err); 90 | } 91 | }); 92 | }) 93 | .on('error', cb) 94 | .end(); 95 | }; 96 | 97 | GoogleContacts.prototype.getContacts = function (cb, params) { 98 | var self = this; 99 | 100 | this._get(_.extend({type: 'contacts'}, params, this.params), receivedContacts); 101 | function receivedContacts(err, data) { 102 | if (err) return cb(err); 103 | 104 | var feed = _.get(data, 'feed', []); 105 | var entry = _.get(data, 'feed.entry', []); 106 | if (!entry.length) { 107 | return cb(null, entry); 108 | } 109 | 110 | self._saveContactsFromFeed(feed); 111 | 112 | var next = false; 113 | _.each(feed.link, function (link) { 114 | if (link.rel === 'next') { 115 | next = true; 116 | var path = url.parse(link.href).path; 117 | self._get({path: path}, receivedContacts); 118 | } 119 | }); 120 | if (!next) { 121 | cb(null, self.contacts); 122 | } 123 | } 124 | }; 125 | 126 | GoogleContacts.prototype.getContact = function (cb, params) { 127 | var self = this; 128 | 129 | if(!_.has(params, 'id')){ 130 | return cb("No id found in params"); 131 | } 132 | 133 | this._get(_.extend({type: 'contacts'}, this.params, params), receivedContact); 134 | 135 | function receivedContact(err, contact) { 136 | if (err) return cb(err); 137 | 138 | cb(null, contact); 139 | } 140 | 141 | }; 142 | 143 | GoogleContacts.prototype._saveContactsFromFeed = function (feed) { 144 | var self = this; 145 | _.each(feed.entry, function (entry) { 146 | var el, url; 147 | if (self.params.thin) { 148 | url = _.get(entry, 'id.$t', ''); 149 | el = { 150 | name: _.get(entry, 'title.$t'), 151 | email: _.get(entry, 'gd$email.0.address'), // only save first email 152 | phoneNumber: _.get(entry, 'gd$phoneNumber.0.uri', '').replace('tel:', ''), 153 | id: url.substring(_.lastIndexOf(url, '/') + 1) 154 | }; 155 | } else { 156 | el = entry; 157 | } 158 | self.contacts.push(el); 159 | }); 160 | }; 161 | 162 | GoogleContacts.prototype._buildPath = function (params) { 163 | if (params.path) return params.path; 164 | 165 | params = _.extend({}, params, this.params); 166 | params.type = params.type || 'contacts'; 167 | params.alt = params.alt || 'json'; 168 | params.projection = params.projection || (params.thin ? 'thin' : 'full'); 169 | params.email = params.email || 'default'; 170 | params['max-results'] = params['max-results'] || 10000; 171 | 172 | var query = { 173 | alt: params.alt 174 | }; 175 | 176 | if(!params.id) query['max-results'] = params['max-results']; 177 | 178 | if (params['updated-min']) 179 | query['updated-min'] = params['updated-min']; 180 | 181 | if (params.q || params.query) 182 | query.q = params.q || params.query; 183 | 184 | var path = '/m8/feeds/'; 185 | path += params.type + '/'; 186 | path += params.email + '/'; 187 | path += params.projection; 188 | if(params.id) path += '/'+ params.id; 189 | path += '?' + qs.stringify(query); 190 | 191 | return path; 192 | }; 193 | 194 | GoogleContacts.prototype.refreshAccessToken = function (refreshToken, params, cb) { 195 | if (typeof params === 'function') { 196 | cb = params; 197 | params = {}; 198 | } 199 | 200 | var data = { 201 | refresh_token: refreshToken, 202 | client_id: this.consumerKey, 203 | client_secret: this.consumerSecret, 204 | grant_type: 'refresh_token' 205 | 206 | }; 207 | 208 | var body = qs.stringify(data); 209 | 210 | var opts = { 211 | host: 'accounts.google.com', 212 | port: 443, 213 | path: '/o/oauth2/token', 214 | method: 'POST', 215 | headers: { 216 | 'Content-Type': 'application/x-www-form-urlencoded', 217 | 'Content-Length': body.length 218 | } 219 | }; 220 | 221 | var req = https.request(opts, function (res) { 222 | var data = ''; 223 | res.on('end', function () { 224 | if (res.statusCode < 200 || res.statusCode >= 300) { 225 | var error = new Error('Bad client request status: ' + res.statusCode); 226 | return cb(error); 227 | } 228 | try { 229 | data = JSON.parse(data); 230 | cb(null, data.access_token); 231 | } 232 | catch (err) { 233 | cb(err); 234 | } 235 | }); 236 | 237 | res.on('data', function (chunk) { 238 | data += chunk; 239 | }); 240 | 241 | res.on('error', cb); 242 | 243 | }).on('error', cb); 244 | 245 | req.write(body); 246 | req.end(); 247 | }; 248 | 249 | exports.GoogleContacts = GoogleContacts; 250 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "google-contacts", 3 | "version": "0.1.5", 4 | "description": "API wrapper for Google Contacts", 5 | "main": "index.js", 6 | "directories": { 7 | "test": "tests" 8 | }, 9 | "scripts": { 10 | "test": "node tests/test.js" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git://github.com/olalonde/Google-Contacts.git" 15 | }, 16 | "keywords": [ 17 | "google", 18 | "contacts", 19 | "api" 20 | ], 21 | "dependencies": { 22 | "debug": "^2.2.0", 23 | "lodash": "^4.5.1" 24 | }, 25 | "author": "Ajnasz ", 26 | "license": "MIT" 27 | } 28 | -------------------------------------------------------------------------------- /tests/test.js: -------------------------------------------------------------------------------- 1 | /*jslint indent:2*/ 2 | /*global require: true, console: true */ 3 | var GoogleContacts = require('../').GoogleContacts; 4 | var assert = require('assert'); 5 | var contactsTested = false; 6 | var contactTested = false; 7 | 8 | var c = new GoogleContacts({ 9 | token: process.env.GOOGLE_TOKEN, 10 | id: process.env.GOOGLE_CONTACT_ID 11 | }); 12 | 13 | c.getContacts(function (err, contacts) { 14 | if (err) throw err; 15 | assert.ok(typeof contacts === 'object', 'Contacts is not an object'); 16 | console.log(contacts); 17 | contactsTested = true; 18 | }); 19 | 20 | c.getContact(function (err, contact) { 21 | if (err) throw err; 22 | assert.ok(typeof contact === 'object', 'Contact is not an object'); 23 | console.log(contact); 24 | contactTested = true; 25 | }); 26 | 27 | process.on('exit', function () { 28 | if (!contactsTested || !contactTested) { 29 | throw new Error('contact test failed'); 30 | } 31 | }); 32 | --------------------------------------------------------------------------------