UNPKG

meetup-api

Version:
539 lines (438 loc) 16.7 kB
/*jslint node: true, maxcomplexity: 7 */ 'use strict'; /* Meetup.js - Node JS Meetup API library This library 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 library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this library. If not, see <http://www.gnu.org/licenses/>. */ /* * Module dependencies */ const request = require('superagent'), websocket = require('websocket-stream'), JSONStream = require('JSONStream'), evStream = require('event-stream'), URLfn = require('url'), endpoints = require('./endpoints.json'), packageJSON = require('../package.json'), Throttle = require('superagent-throttle'), requireOpt = require('./myUtils').requireOpt; /* * Optional module dependencies */ const ProxyAgent = requireOpt('proxy-agent')['proxy-agent'], OAuth = requireOpt(['oauth', 'superagent-oauth'], (modules) => { return modules && modules['superagent-oauth'] && (modules['superagent-oauth'](request) || true) && modules.oauth || null; }); /* * Module private variables */ let MeetupAPIkey, ProxyURI; /* * Constructor * * @params * {key} Meetup API key * {oath}{key, secret} Meetup API oauth object * @return {Object} new instance */ class Meetup { // Meetup contructor constructor(params) { // Assign Meetup auth data MeetupAPIkey = params && (params.key || params.oauth || null); Meetup.oauth = null; Meetup.oauth2 = null; } // create metadata properties get commands() { return Object.freeze(Object.keys(Meetup.prototype)); // delete this } get version() { return Object.freeze(packageJSON.version); } get name() { return Object.freeze(packageJSON.name); } get description() { return Object.freeze(packageJSON.description); } get dependencies() { return Object.freeze(packageJSON.dependencies); } get optionalDependencies() { return Object.freeze(packageJSON.optionalDependencies); } get repository() { return Object.freeze(packageJSON.repository); } get authkey() { return !!MeetupAPIkey; } set authkey(auth) { MeetupAPIkey = auth || null; } get proxy() { return !!(ProxyAgent && ProxyURI); } set proxy(uri) { ProxyURI = (ProxyAgent && uri) ? uri : null; } [Symbol.iterator]() { let index = -1, commands = Object.keys(Meetup.prototype); return { next: () => ({ value: commands[++index], done: index === commands.length }) }; } // OAUTH functions getOAuthRequestToken(callback) { let errMessage = (MeetupAPIkey && MeetupAPIkey.key && MeetupAPIkey.secret) ? null : 'No OAuth Keys'; errMessage = (OAuth) ? errMessage : 'No OAuth support'; if (errMessage) { callback({ message: errMessage }, null); } else { Meetup.oauth = new OAuth.OAuth( 'https://api.meetup.com/oauth/request/', 'https://api.meetup.com/oauth/access/', MeetupAPIkey.key, MeetupAPIkey.secret, '1.0', null, 'HMAC-SHA1' ); return Meetup.oauth.getOAuthRequestToken({}, (error, oauth_token, oauth_token_secret) => { let authorizeURL = null; if (!error) { MeetupAPIkey.oauth_token = oauth_token; MeetupAPIkey.oauth_token_secret = oauth_token_secret; authorizeURL = 'http://www.meetup.com/authorize/?oauth_token=' + oauth_token; } callback(error, authorizeURL); }); } } getOAuthAccessToken(oauth_verifier, callback) { return Meetup.oauth.getOAuthAccessToken( MeetupAPIkey.oauth_token, MeetupAPIkey.oauth_token_secret, oauth_verifier, (error, access_token, access_token_secret) => { if (error) { callback(error); } else { MeetupAPIkey.access_token = access_token; MeetupAPIkey.access_token_secret = access_token_secret; callback(null); } } ); } getOAuth2RequestToken(params, callback) { let errMessage = (OAuth) ? (MeetupAPIkey && MeetupAPIkey.key && MeetupAPIkey.secret) ? null : 'No OAuth Keys' : 'No OAuth support'; if (errMessage) { callback({ message: errMessage }, null); } else { Meetup.oauth2 = new OAuth.OAuth2( MeetupAPIkey.key, MeetupAPIkey.secret, 'https://secure.meetup.com/', 'oauth2/authorize', 'oauth2/access', null ); MeetupAPIkey.redirect = params.redirect; let authURL = Meetup.oauth2.getAuthorizeUrl({ response_type: params.response_type || 'code', redirect_uri: params.redirect }); if (authURL) { callback(null, authURL); } else { callback({ message: 'No authURL return' }, null); } } } getOAuth2AccessToken(oauth_verifier, callback) { if (typeof oauth_verifier === 'function') { callback = oauth_verifier; oauth_verifier = null; } var oauth2Params = (MeetupAPIkey.access_token && MeetupAPIkey.refresh_token) ? { grant_type: 'refresh_token' } : { grant_type: 'authorization_code', redirect_uri: MeetupAPIkey.redirect }; Meetup.oauth2.getOAuthAccessToken( oauth_verifier || MeetupAPIkey.refresh_token, oauth2Params, (error, access_token, refresh_token) => { if (error) { callback(error); } else { MeetupAPIkey.access_token = access_token; MeetupAPIkey.refresh_token = refresh_token; callback(null); } } ); } refreshOAuth2AccessToken(refresh_token, callback) { console.warn('WARNING: refreshOAuth2AccessToken() is deprecated use getOAuth2AccessToken() instead!'); if (MeetupAPIkey.refresh_token) { this.getOAuth2AccessToken(refresh_token, callback); } } } /* * Request functions */ class APIRequest { // Preprocess params (used by 'Create Meetup prototypes') static preProcess(obj) { for (let i in obj) { if (obj[i].constructor.name.toLowerCase() === 'array') { obj[i] = obj[i].join(','); } } } // Create request object (used by 'HTTP(S) request') static create(method, url, params) { // insert meetupAPIkey only if it exists params.key = (MeetupAPIkey && (typeof MeetupAPIkey === 'string')) && MeetupAPIkey; // supply url vars let thisUrl = URLfn.parse(url, true), urlVars = thisUrl.pathname.match(/\/:\w+/g) || []; // disable url.search to force URL.format to use url.query thisUrl.search = null; // replace url variables with parameters data and delete the populated properties from params urlVars.forEach( urlVar => { thisUrl.pathname = thisUrl.pathname.replace(urlVar, '/' + params[urlVar.substr(2)]); delete params[urlVar.substr(2)]; } ); // Setup throttle params let throttle = new Throttle({ active: true, // set false to pause queue rate: 2, // how many requests can be sent every `ratePer` ratePer: 8000, // number of ms in which `rate` requests may be sent concurrent: 2 // how many requests can be sent concurrently }) let req = request(method, URLfn.format(thisUrl)) .use(throttle.plugin()) .set('User-Agent', packageJSON.name + '/NodejsClientLib/' + packageJSON.version); let agent = APIRequest.createProxyAgent(thisUrl); req._agent = agent; // return request object return req; } // Stream event static stream(req, callback) { // create stream req.pipe(JSONStream.parse()).pipe( // return data on event evStream.map(data => callback(data)) ); } // Add Oauth params (used by 'HTTP(S) request') static addOauthParams(req, params) { if (typeof MeetupAPIkey === 'object') { if (Meetup.oauth) { //add oauth keys req.sign( Meetup.oauth, params && params.access_token_key || MeetupAPIkey.access_token, params && params.access_token_secret || MeetupAPIkey.access_token_secret ); } if (Meetup.oauth2) { //add oauth2 bearer let bearer = params && params.access_token_key || MeetupAPIkey.access_token; req.set('Authorization', 'Bearer ' + bearer); } } } // create proxy agent object static createProxyAgent(url) { let agent = null; if (ProxyAgent && ProxyURI) { // create an instance of the `ProxyAgent` class with the proxy server information let opts = URLfn.parse(ProxyURI); // IMPORTANT! Set the `secureEndpoint` option to `false` when connecting // over "ws://", but `true` when connecting over "wss://" opts.secureEndpoint = (url.protocol) ? (url.protocol === 'wss:' || url.protocol === 'https:') : false; agent = new ProxyAgent(opts); } return agent; } // HTTP(S) request static http(endpoint, params, callback) { // preprocess parameters APIRequest.preProcess(params); // create request let req = APIRequest.create(endpoint.method, endpoint.resource, params); // add OAUTH parameters for request APIRequest.addOauthParams.call(this, req, params); // add MultiPart photo upload if (params.photo && endpoint.multipart_photo) { req.attach('photo', params.photo); delete params.photo; } // add query params & req.query(params) .set('Accept', '*/*') .set('User-Agent', packageJSON.name + '/NodejsClientLib/' + packageJSON.version) .buffer(true); // execute the request if (!endpoint.chunked) { req.end( (err, res) => { let response = null; if (!err) { try { response = (res.statusCode === 204 ? '' : ((!Object.keys(res.body).length) ? JSON.parse(res.text) : res.body)); } catch (error) { response = null; err = error; err.response = res; } if (res.header['x-ratelimit-limit']) { response = response || {}; response.ratelimit = { limit: res.header['x-ratelimit-limit'], remaining: res.header['x-ratelimit-remaining'], reset: res.header['x-ratelimit-reset'] }; } } callback(err || res.error, response); } ); } // request funtions this.abort = function() { req.abort(); return this; }; this.timeout = function(ms) { ms = (params.callback) ? 0 : ms; req.timeout(ms); return this; }; // pass callback to request events & create stream on chunked HTTP endpoints this.on = function(event, callback) { if (endpoint.chunked && event === 'data') { APIRequest.stream(req, callback); } else { req.on(event, callback); } return this; }; // listeners functions ['removeListener', 'removeAllListeners', 'setMaxListeners'].forEach(fnct => { this[fnct] = req[fnct]; return this; }); } // WebSocket request static ws(endpoint, params) { // preprocess parameters APIRequest.preProcess(params); // supply url vars let url = URLfn.parse(endpoint.resource, true); // add query to url url.query = Object.assign(url.query, params); // disable url.search to force URL.format to use url.query url.search = null; url = URLfn.format(url); // define websocket options let wsoptions = {}, agent = APIRequest.createProxyAgent(url); wsoptions.agent = agent; // create websocket connection let ws = websocket(url, wsoptions); // abort websocket stream this.abort = function() { ws.destroy(); return this; }; // pass websocket events this.on = function(event, callback) { if (event === 'data') { ws.on(event, data => { // parse buffer string to JSON and return to the callback callback(JSON.parse(data.toString())); }); } else { // pass call back to websocket event ws.on(event, callback); } return this; }; // listeners functions let listeners = [ 'removeListener', 'removeAllListeners', 'setMaxListeners' ]; listeners.forEach(fnct => { this[fnct] = ws[fnct]; return this; }); } } /* * Create Meetup prototypes */ Object.keys(endpoints).forEach( key => { Meetup.prototype[key] = function(params, callback) { if (!endpoints[key].disable) { // parse endpoint url and get the protocol (without ':') let url = URLfn.parse(endpoints[key].resource), reqprotocol = url.protocol.replace(':', ''); // replace 'https' with 'http' to use the same request function reqprotocol = (reqprotocol === 'https') ? 'http' : reqprotocol; // replace 'wss' with 'ws' to use the same request function reqprotocol = (reqprotocol === 'wss') ? 'ws' : reqprotocol; // assign param function to callback if ('function' === typeof params) { callback = params; params = {}; } APIRequest[reqprotocol].call(this, endpoints[key], params, callback); } else if (endpoints[key].comments) { callback(endpoints[key].comments, null); } // return Meetup object return this; }; } ); /* * Export new constructor wrapper */ module.exports = function(params) { // return new Meetup object on require return new Meetup(params); };