UNPKG

hangouts-js

Version:

Google Hangouts in node

411 lines (350 loc) 10.7 kB
'use strict'; var Util = require('./util'); var xmppClient = require('node-xmpp-client'); var Stanza = require('node-xmpp-core').Stanza; var request = require('request'); var cheerio = require('cheerio'); var EventEmitter = require('events').EventEmitter; request = request.defaults({ rejectUnauthorized: false }); function extractData(body, callback) { var response = {}; var code = /eval\('([^']+)'\)/.exec(body); var generatedObject = Util.evalInSandbox(code[0]); var uniqueCode = /new botguard.bg\('([^']+)'\)/.exec(body); generatedObject.document.bg = new generatedObject.botguard.bg(uniqueCode[1]); generatedObject.document.bg.invoke(function(bgresponse) { response.bgresponse = bgresponse; var $ = cheerio.load(body); $('input').each(function() { var name = this.attribs.name; if (name !== 'bgresponse') { response[name] = this.attribs.value; } }); callback(response); }); } function Messenger() { // Make sure we call our parents constructor EventEmitter.call(this); this.config = { thumbnailHeight: 70, autoAcceptFriendRequests: true, recoveryEmail: '' }; this.sessionData = { userData: { email: '' }, sessionID: '', cookies: request.jar(), isInvisible: false, friendsList: {} }; return this; } // The Messenger emits events! Messenger.prototype = Object.create(EventEmitter.prototype); Messenger.prototype.setConfig = function setConfig(userConfig) { if (typeof userConfig == 'undefined') userConfig = {}; for (var configItem in this.config) if (userConfig[configItem]) this.config[configItem] = userConfig[configItem]; } Messenger.prototype.connection = null; Messenger.prototype.newInstance = function newInstance() { var me = this; me.sessionData = { userData: { email: '' }, sessionID: '', cookies: request.jar(), isInvisible: false, friendsList: {} }; }; function unlockAccount (callback) { var me = this; request.post({ url: 'https://accounts.google.com/LoginVerification', jar: me.sessionData.cookies }, function (error, response, body) { if (error || response.statusCode !== 200) { throw error; } extractData(body, function(postFields) { postFields.challengetype = 'RecoveryEmailChallenge'; postFields.emailAnswer = me.config.recoveryEmail; request.post({ url: 'https://accounts.google.com/LoginVerification', jar: me.sessionData.cookies }, function (error, response) { if (error || (response.statusCode != 200 && response.statusCode != 302)) { throw error; } if (response.statusCode == 200) { console.log('Couldn\t unlock the account.'); } else { request({ url: 'https://accounts.google.com/FinishSignIn', jar: me.sessionData.cookies }, function (error, response) { console.log('Go https://security.google.com/settings/security/activity?pli=1 and allow this device.'); }); } }).form(postFields); }); }); } /** * Logins to Google server * @param {Object} post Post parameters of login * @param {Function} callback A function to pass auth object to it * @throws {errnoException} If page is not accessible */ Messenger.prototype.authenticate = function authenticate(post, callback) { var me = this; request.post({ url: 'https://accounts.google.com/ServiceLogin', jar: this.sessionData.cookies }, function (error, response, body) { if (!error && response.statusCode !== 302) { error = /class="error-msg"[^>]*>([^<]+)/.exec(body)[1].trim(); } if (error) { throw error; } if (response.headers.location == 'https://accounts.google.com/LoginVerification') { console.log('Account is locked. trying to unlock...'); unlockAccount.call(me, callback); } else { response = null; body = null; callback(); } }).form(post); }; /** * Logins to Google server * @param {string} email Full Google email address * @param {string} password Password of email * @param {boolean} isVisible Should user login as visible or not * @param {Function} callback A function to pass auth object to it * @throws {errnoException} If page is not accessible */ Messenger.prototype.login = function login(email, password, isVisible, callback) { var me = this; me.sessionData.isInvisible = !isVisible; request.get({ url: 'https://accounts.google.com/ServiceLogin', jar: this.sessionData.cookies }, function (error, response, body) { if (error || response.statusCode !== 200) { throw error; } extractData(body, function (response) { me.sessionData.userData.email = email; response.Email = email; response.Passwd = password; me.authenticate(response, function() { me.connection = new xmppClient({ jid: email, password: password, host: "talk.google.com" }); me.connection.on('stanza', function(stanza) { onStanza.call(me, stanza); }); me.connection.on('error', function(e) { console.log(e); }); me.connection.on('online', function() { me.connection.send(new Stanza('presence', {}) .c('show').t('chat') .up() .c('status').t(me.sessionData.isInvisible || 'Online') ); var rosterElem = new Stanza('iq', { 'from': me.connection.jid, 'type': 'get', 'id': 'google-roster' }).c('query', { 'xmlns': 'jabber:iq:roster', 'xmlns:gr': 'google:roster', 'gr:ext': '2' }); me.connection.send(rosterElem); callback(); }); }); }); }); }; Messenger.prototype.httpRequest = function httpRequest(url, callback) { request({ url: url, jar: this.sessionData.cookies }, function (error, response, body) { if (error) { console.error('Error:', error.message); } else if (response.statusCode !== 200) { console.error('HTTP Status:', response.statusCode); } else { callback(body); } }); }; Messenger.prototype.sendMessage = function sendMessage(to, message) { var me = this; var stanza = new Stanza('message', { from: me.connection.jid, to: to, type: 'chat' }) .c('body') .t(message); this.connection.send(stanza); return me; } Messenger.prototype.addFriend = function addFriend(to, nickName) { var me = this; var stanza = new Stanza('presence', { from: me.connection.jid, to: to, type: 'subscribe' }); if (nickName) { stanza = stanza .c('nick:nick', { 'xmlns:nick': 'http://etherx.jabber.org/streams' }) .t(nickName); } this.connection.send(stanza); return me; } Messenger.prototype.unFriend = function unFriend(to) { var me = this; var stanza = new Stanza('presence', { from: me.connection.jid, to: to, type: 'unsubscribe' }); this.connection.send(stanza); return me; } function onStanza(stanza) { var me = this; //console.log(stanza); if (stanza.is('message')) { if ((stanza.attrs.type !== 'error') && (stanza.getChildText('body'))) { var message = stanza.getChildText('body'); var imagepattern = /https:\/\/plus\.google\.com\/photos\/albums\/[^\?]+\?pid=(\d+)&oid=(\d+)/i; if (imagepattern.test(message)) { // https://plus.google.com/photos/albums/ddfsdfsdf?pid=6139223141515375650&oid=102569971011649807618 var params = imagepattern.exec(message); var xmlUrl = 'https://picasaweb.google.com/data/feed/api/user/' + params[2] + '/photoid/' + params[1]; message = message.replace(imagepattern, '').trim(); me.httpRequest(xmlUrl, function callback(body) { var image = ''; var thumbnail = ''; var size = /<gphoto:size>(\d+)/.exec(body); size = size ? size[1] : null; var image = /<media:content url='([^']+' )/.exec(body); // there are sill 'height', 'width', 'type' (mime type) and 'medium'='image' attributes too image = image ? image[1] : null; var thumbnail = null; var filename = ''; if (image) { thumbnail = image.split('/'); filename = thumbnail.pop(); thumbnail.push('s' + me.config.thumbnailHeight); thumbnail.push(filename); thumbnail = thumbnail.join('/'); } me.emit('image', stanza.attrs.from.split('/')[0], filename, image, thumbnail, size, message ); }); } else { var fromParts = stanza.attrs.from.split('/'); me.emit('message', fromParts[0], message, (fromParts.length > 1) ? fromParts[1] : '' ); } } if (stanza.getChild('composing')) me.emit('typing', stanza.attrs.from.split('/')[0], 'typing' ); if (stanza.getChild('paused')) me.emit('typing', stanza.attrs.from.split('/')[0], 'typed' ); if (stanza.getChild('inactive')) me.emit('typing', stanza.attrs.from.split('/')[0], 'clear-typing' ); } if (stanza.is('presence')) { if (stanza.attrs.type === 'subscribe') { me.emit('friend-request', stanza.attrs.from.split('/')[0] ); if (me.config.autoAcceptFriendRequests) { stanza.attrs.to = stanza.attrs.from; delete stanza.attrs.from; Messenger.connection.send(stanza); } } else if (stanza.attrs.type === 'subscribed') { var nick = stanza.getChildText('show'); me.emit('friended', stanza.attrs.from.split('/')[0], nick || null ); } else { var status = stanza.getChildText('status') || ''; var customStatus = stanza.getChildText('show') || ''; var caps = stanza.getChildText('c'); if (caps) caps = caps.attrs.ext.split(/\s/i); else caps = []; var photo = stanza.getChildText('x'); if (photo) photo = photo.getChildText('photo'); else photo = null; me.emit('status-changed', stanza.attrs.from.split('/')[0], status, customStatus, caps, photo ); } } }; module.exports = (new Messenger()); // singleton