hangouts-js
Version:
Google Hangouts in node
411 lines (350 loc) • 10.7 kB
JavaScript
;
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