Skip to content

Instantly share code, notes, and snippets.

@ztoben
Last active October 25, 2018 17:05
Show Gist options
  • Save ztoben/41bd68f893f46fc055f52bbacbee30d2 to your computer and use it in GitHub Desktop.
Save ztoben/41bd68f893f46fc055f52bbacbee30d2 to your computer and use it in GitHub Desktop.
Fix connection issues in hangupsjs
// Generated by CoffeeScript 1.12.7
(function() {
var ABORT, ALIVE_WAIT, Auth, CLIENT_GET_CONVERSATION_RESPONSE, CLIENT_GET_ENTITY_BY_ID_RESPONSE, CLIENT_SYNC_ALL_NEW_EVENTS_RESPONSE, Channel, ChatReq, Client, ClientDeliveryMediumType, ClientNotificationLevel, CookieJar, DEFAULTS, EventEmitter, FileCookieStore, FocusStatus, IMAGE_UPLOAD_URL, Init, MessageActionType, MessageBuilder, MessageParser, None, OffTheRecordStatus, Q, TypingStatus, aliases, datetolong, fmterr, fs, log, plug, randomid, ref, ref1, rm, syspath, togoogtime, touch, wait,
bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
hasProp = {}.hasOwnProperty;
require('fnuc').expose(global);
FileCookieStore = require('tough-cookie-file-store');
CookieJar = require('tough-cookie').CookieJar;
EventEmitter = require('events').EventEmitter;
syspath = require('path');
log = require('bog');
fs = require('fs');
Q = require('q');
ref = require('./util'), plug = ref.plug, fmterr = ref.fmterr, wait = ref.wait;
MessageBuilder = require('./messagebuilder');
MessageParser = require('./messageparser');
ChatReq = require('./chatreq');
Channel = require('./channel');
Auth = require('./auth');
Init = require('./init');
ref1 = require('./schema'), OffTheRecordStatus = ref1.OffTheRecordStatus, FocusStatus = ref1.FocusStatus, TypingStatus = ref1.TypingStatus, MessageActionType = ref1.MessageActionType, ClientDeliveryMediumType = ref1.ClientDeliveryMediumType, ClientNotificationLevel = ref1.ClientNotificationLevel, CLIENT_SYNC_ALL_NEW_EVENTS_RESPONSE = ref1.CLIENT_SYNC_ALL_NEW_EVENTS_RESPONSE, CLIENT_GET_CONVERSATION_RESPONSE = ref1.CLIENT_GET_CONVERSATION_RESPONSE, CLIENT_GET_ENTITY_BY_ID_RESPONSE = ref1.CLIENT_GET_ENTITY_BY_ID_RESPONSE;
IMAGE_UPLOAD_URL = 'https://docs.google.com/upload/photos/resumable';
DEFAULTS = {
rtokenpath: syspath.normalize(syspath.join(__dirname, '../refreshtoken.txt')),
cookiespath: syspath.normalize(syspath.join(__dirname, '../cookies.json'))
};
ALIVE_WAIT = 45000;
touch = function(path) {
var err;
try {
return fs.statSync(path);
} catch (error) {
err = error;
if (err.code === 'ENOENT') {
return fs.writeFileSync(path, '');
}
}
};
rm = function(path) {
return Q.Promise(function(rs, rj) {
return fs.unlink(path, plug(rs, rj));
}).fail(function(err) {
if (err.code === 'ENOENT') {
return null;
} else {
return Q.reject(err);
}
});
};
None = void 0;
randomid = function() {
return Math.round(Math.random() * Math.pow(2, 32));
};
datetolong = function(d) {
if (typeis(d, 'date')) {
return d.getTime();
} else {
return d;
}
};
togoogtime = sequence(datetolong, mul(1000));
ABORT = {
abort: true
};
module.exports = Client = (function(superClass) {
extend(Client, superClass);
function Client(opts) {
this.uploadimage = bind(this.uploadimage, this);
this.isInited = bind(this.isInited, this);
this.ensureConnected = bind(this.ensureConnected, this);
this.logout = bind(this.logout, this);
this.opts = mixin(DEFAULTS, opts);
this.doInit();
this.messageParser = new MessageParser(this);
this.on('clientid', (function(_this) {
return function(clientid) {
return _this.init.clientid = clientid;
};
})(this));
}
Client.prototype.loglevel = function(lvl) {
return log.level(lvl);
};
Client.prototype.connect = function(creds) {
this.emit('connecting');
this.auth = new Auth(this.jar, this.jarstore, creds, this.opts);
return this.auth.getAuth().then((function(_this) {
return function() {
return _this.channel.fetchPvt();
};
})(this)).then((function(_this) {
return function(pvt) {
if (!pvt) {
_this.logout().then(function() {
return _this.connect(creds);
});
return Q.reject(ABORT);
}
return _this.init.initChat(_this.jarstore, pvt);
};
})(this)).then((function(_this) {
return function() {
return _this.initrecentconversations(_this.init);
};
})(this)).then((function(_this) {
return function() {
var poller;
_this.running = true;
_this.connected = false;
_this.lastActive = Date.now();
_this.ensureConnected();
(poller = function() {
if (!_this.running) {
return;
}
return _this.channel.getLines().then(function(lines) {
if (!_this.connected && _this.running) {
_this.connected = true;
_this.emit('connected');
}
if (_this.running) {
_this.messageParser.parsePushLines(lines);
return poller();
}
}).fail(function(err) {
if (err.stack) {
log.debug(err.stack);
}
log.debug(err);
log.info('poller stopped', fmterr(err));
_this.running = false;
_this.connected = false;
return _this.emit('connect_failed', err);
});
})();
return Q.Promise(function(rs) {
return _this.once('connected', function() {
return rs();
});
});
};
})(this)).fail((function(_this) {
return function(err) {
_this.running = false;
_this.connected = false;
if (err === ABORT) {
return null;
} else {
_this.emit('connect_failed', err);
return Q.reject(err);
}
};
})(this));
};
Client.prototype.doInit = function() {
var ref2;
if (!this.opts.jarstore) {
touch(this.opts.cookiespath);
}
this.jarstore = (ref2 = this.opts.jarstore) != null ? ref2 : new FileCookieStore(this.opts.cookiespath);
this.jar = new CookieJar(this.jarstore);
this.channel = new Channel(this.jarstore, this.opts.proxy);
this.init = new Init(this.opts.proxy);
return this.chatreq = new ChatReq(this.jarstore, this.init, this.channel, this.opts.proxy);
};
Client.prototype.logout = function() {
var cpath, rpath;
this.disconnect();
rpath = this.opts.rtokenpath;
cpath = this.opts.cookiespath;
return Q().then(function() {
log.info('removing refresh token');
return rm(rpath);
}).then(function() {
log.info('removing cookie store');
return rm(cpath);
}).then((function(_this) {
return function() {
return _this.doInit();
};
})(this));
};
Client.prototype.emit = function(ev, data) {
if (ev !== 'connect_failed') {
this.lastActive = Date.now();
}
log.debug('emit', ev, data != null ? data : '');
return Client.__super__.emit.apply(this, arguments);
};
Client.prototype.ensureConnected = function() {
if (this.ensureTimer) {
clearTimeout(this.ensureTimer);
}
if (!this.running) {
return;
}
return Q().then((function(_this) {
return function() {
if ((Date.now() - _this.lastActive) > ALIVE_WAIT) {
log.debug('activity wait timeout after 45 secs');
_this.disconnect();
return _this.emit('connect_failed', new Error("Connection timeout"));
}
};
})(this)).then((function(_this) {
return function() {
var waitFor;
if (!_this.running) {
return;
}
waitFor = _this.lastActive + ALIVE_WAIT - Date.now();
return _this.ensureTimer = setTimeout(_this.ensureConnected, waitFor);
};
})(this));
};
Client.prototype.disconnect = function() {
var ref2;
log.debug('disconnect');
this.running = false;
this.connected = false;
if (this.ensureTimer) {
clearTimeout(this.ensureTimer);
}
return (ref2 = this.channel) != null ? typeof ref2.stop === "function" ? ref2.stop() : void 0 : void 0;
};
Client.prototype.isInited = function() {
return !!(this.init.apikey && this.init.email && this.init.headerdate && this.init.headerversion && this.init.headerid && this.init.clientid);
};
Client.prototype._requestBodyHeader = function() {
return [[6, 3, this.init.headerversion, this.init.headerdate], [this.init.clientid, this.init.headerid], None, "en"];
};
Client.prototype.setactiveclient = function(active, timeoutsecs) {
return this.chatreq.req('clients/setactiveclient', [this._requestBodyHeader(), active, this.init.email + "/" + this.init.clientid, timeoutsecs]);
};
Client.prototype.syncallnewevents = function(timestamp) {
return this.chatreq.req('conversations/syncallnewevents', [this._requestBodyHeader(), togoogtime(timestamp), [], None, [], false, [], 1048576], false).then(function(body) {
return CLIENT_SYNC_ALL_NEW_EVENTS_RESPONSE.parse(body);
});
};
Client.prototype.sendchatmessage = function(conversation_id, segments, image_id, otr_status, client_generated_id, delivery_medium, message_action_type) {
if (image_id == null) {
image_id = None;
}
if (otr_status == null) {
otr_status = OffTheRecordStatus.ON_THE_RECORD;
}
if (client_generated_id == null) {
client_generated_id = null;
}
if (delivery_medium == null) {
delivery_medium = [ClientDeliveryMediumType.BABEL];
}
if (message_action_type == null) {
message_action_type = [[MessageActionType.NONE, ""]];
}
if (!client_generated_id) {
client_generated_id = randomid();
}
return this.chatreq.req('conversations/sendchatmessage', [this._requestBodyHeader(), None, None, None, message_action_type, [segments, []], (image_id ? [[image_id, false]] : None), [[conversation_id], client_generated_id, otr_status, delivery_medium], None, None, None, []]);
};
Client.prototype.getselfinfo = function() {
return this.chatreq.req('contacts/getselfinfo', [this._requestBodyHeader(), [], []]);
};
Client.prototype.setfocus = function(conversation_id, focus, timeoutsecs) {
if (focus == null) {
focus = FocusStatus.FOCUSED;
}
if (timeoutsecs == null) {
timeoutsecs = 20;
}
return this.chatreq.req('conversations/setfocus', [this._requestBodyHeader(), [conversation_id], focus, timeoutsecs]);
};
Client.prototype.settyping = function(conversation_id, typing) {
if (typing == null) {
typing = TypingStatus.TYPING;
}
return this.chatreq.req('conversations/settyping', [this._requestBodyHeader(), [conversation_id], typing]);
};
Client.prototype.setpresence = function(online, mood) {
if (mood == null) {
mood = None;
}
return this.chatreq.req('presence/setpresence', [this._requestBodyHeader(), [720, online ? 1 : 40], None, None, [!online], [mood]]);
};
Client.prototype.querypresence = function(chat_id) {
return this.chatreq.req('presence/querypresence', [this._requestBodyHeader(), [[chat_id]], [1, 2, 5, 7, 8]]);
};
Client.prototype.removeuser = function(conversation_id) {
var client_generated_id;
client_generated_id = randomid();
return this.chatreq.req('conversations/removeuser', [this._requestBodyHeader(), None, None, None, [[conversation_id], client_generated_id, 2]]);
};
Client.prototype.deleteconversation = function(conversation_id) {
return this.chatreq.req('conversations/deleteconversation', [this._requestBodyHeader(), [conversation_id], Date.now() * 1000, None, []]);
};
Client.prototype.updatewatermark = function(conversation_id, timestamp) {
return this.chatreq.req('conversations/updatewatermark', [this._requestBodyHeader(), [conversation_id], togoogtime(timestamp)]);
};
Client.prototype.adduser = function(conversation_id, chat_ids) {
var chat_id, client_generated_id;
client_generated_id = randomid();
return this.chatreq.req('conversations/adduser', [
this._requestBodyHeader(), None, (function() {
var i, len, results;
results = [];
for (i = 0, len = chat_ids.length; i < len; i++) {
chat_id = chat_ids[i];
results.push([chat_id, None, None, "unknown", None, []]);
}
return results;
})(), None, [[conversation_id], client_generated_id, 2, None, 4]
]);
};
Client.prototype.renameconversation = function(conversation_id, name) {
var client_generated_id;
client_generated_id = randomid();
return this.chatreq.req('conversations/renameconversation', [this._requestBodyHeader(), None, name, None, [[conversation_id], client_generated_id, 1]]);
};
Client.prototype.createconversation = function(chat_ids, force_group) {
var chat_id, client_generated_id;
if (force_group == null) {
force_group = false;
}
client_generated_id = randomid();
return this.chatreq.req('conversations/createconversation', [
this._requestBodyHeader(), chat_ids.length === 1 && !force_group ? 1 : 2, client_generated_id, None, (function() {
var i, len, results;
results = [];
for (i = 0, len = chat_ids.length; i < len; i++) {
chat_id = chat_ids[i];
results.push([chat_id, None, None, "unknown", None, []]);
}
return results;
})()
]);
};
Client.prototype.getconversation = function(conversation_id, timestamp, max_events, include_metadata) {
if (max_events == null) {
max_events = 50;
}
if (include_metadata == null) {
include_metadata = false;
}
return this.chatreq.req('conversations/getconversation', [this._requestBodyHeader(), [[conversation_id], [], []], include_metadata, true, None, max_events, [None, None, togoogtime(timestamp)]], false).then(function(body) {
return CLIENT_GET_CONVERSATION_RESPONSE.parse(body);
});
};
Client.prototype.syncrecentconversations = function(timestamp_since) {
if (timestamp_since == null) {
timestamp_since = null;
}
return this.chatreq.req('conversations/syncrecentconversations', [this._requestBodyHeader(), timestamp_since], false).then(function(body) {
return CLIENT_SYNC_ALL_NEW_EVENTS_RESPONSE.parse(body);
});
};
Client.prototype.initrecentconversations = function(init) {
return this.chatreq.req('conversations/syncrecentconversations', [this._requestBodyHeader(), null], false).then(function(body) {
var data;
data = CLIENT_SYNC_ALL_NEW_EVENTS_RESPONSE.parse(body);
return init.conv_states = data.conversation_state;
});
};
Client.prototype.searchentities = function(search_string, max_results) {
if (max_results == null) {
max_results = 10;
}
return this.chatreq.req('contacts/searchentities', [this._requestBodyHeader(), [], search_string, max_results]);
};
Client.prototype.getentitybyid = function(chat_ids) {
var chat_id;
return this.chatreq.req('contacts/getentitybyid', [
this._requestBodyHeader(), None, (function() {
var i, len, results;
results = [];
for (i = 0, len = chat_ids.length; i < len; i++) {
chat_id = chat_ids[i];
results.push([String(chat_id)]);
}
return results;
})()
], false).then(function(body) {
return CLIENT_GET_ENTITY_BY_ID_RESPONSE.parse(body);
});
};
Client.prototype.sendeasteregg = function(conversation_id, easteregg) {
return this.chatreq.req('conversations/easteregg', [this._requestBodyHeader(), [conversation_id], [easteregg, None, 1]]);
};
Client.prototype.setconversationnotificationlevel = function(conversation_id, level) {
return this.chatreq.req('conversations/setconversationnotificationlevel', [this._requestBodyHeader(), [conversation_id], level]);
};
Client.prototype.modifyotrstatus = function(conversation_id, otr) {
var client_generated_id;
if (otr == null) {
otr = OffTheRecordStatus.ON_THE_RECORD;
}
client_generated_id = randomid();
return this.chatreq.req('conversations/modifyotrstatus', [this._requestBodyHeader(), None, otr, None, [[conversation_id], client_generated_id, otr, None, 9]]);
};
Client.prototype.uploadimage = function(imagefile, filename, timeout) {
var chatreq, puturl, size;
if (filename == null) {
filename = null;
}
if (timeout == null) {
timeout = 30000;
}
filename = filename != null ? filename : syspath.basename(imagefile);
size = null;
puturl = null;
chatreq = this.chatreq;
return Q().then(function() {
return Q.Promise(function(rs, rj) {
return fs.stat(imagefile, plug(rs, rj));
});
}).then(function(st) {
return size = st.size;
}).then(function() {
log.debug('image resume upload prepare');
return chatreq.baseReq(IMAGE_UPLOAD_URL, 'application/x-www-form-urlencoded;charset=UTF-8', {
protocolVersion: "0.8",
createSessionRequest: {
fields: [
{
external: {
filename: filename,
size: size,
put: {},
name: 'file'
}
}
]
}
});
}).then(function(body) {
var ref2, ref3, ref4, ref5;
puturl = body != null ? (ref2 = body.sessionStatus) != null ? (ref3 = ref2.externalFieldTransfers) != null ? (ref4 = ref3[0]) != null ? (ref5 = ref4.putInfo) != null ? ref5.url : void 0 : void 0 : void 0 : void 0 : void 0;
return log.debug('image resume upload to:', puturl);
}).then(function() {
return Q.Promise(function(rs, rj) {
return fs.readFile(imagefile, plug(rs, rj));
});
}).then(function(buf) {
log.debug('image resume uploading');
return chatreq.baseReq(puturl, 'application/octet-stream', buf, true, timeout);
}).then(function(body) {
var ref2, ref3, ref4, ref5, ref6;
log.debug('image resume upload finished');
return body != null ? (ref2 = body.sessionStatus) != null ? (ref3 = ref2.additionalInfo) != null ? (ref4 = ref3['uploader_service.GoogleRupioAdditionalInfo']) != null ? (ref5 = ref4.completionInfo) != null ? (ref6 = ref5.customerSpecificInfo) != null ? ref6.photoid : void 0 : void 0 : void 0 : void 0 : void 0 : void 0;
});
};
return Client;
})(EventEmitter);
aliases = ['logLevel', 'sendChatMessage', 'setActiveClient', 'syncAllNewEvents', 'getSelfInfo', 'setConversationNotificationLevel', 'modifyOtrStatus', 'setFocus', 'setTyping', 'setPresence', 'queryPresence', 'removeUser', 'deleteConversation', 'updateWatermark', 'addUser', 'renameConversation', 'createConversation', 'getConversation', 'syncRecentConversations', 'searchEntities', 'getEntityById', 'sendEasteregg', 'uploadImage'];
aliases.forEach(function(alias) {
return Client.prototype[alias] = Client.prototype[alias.toLowerCase()];
});
Client.OffTheRecordStatus = OffTheRecordStatus;
Client.ClientDeliveryMediumType = ClientDeliveryMediumType;
Client.FocusStatus = FocusStatus;
Client.TypingStatus = TypingStatus;
Client.MessageActionType = MessageActionType;
Client.MessageBuilder = MessageBuilder;
Client.authStdin = Auth.prototype.authStdin;
Client.NotificationLevel = ClientNotificationLevel;
Client.OAUTH2_LOGIN_URL = Auth.OAUTH2_LOGIN_URL;
}).call(this);
// Generated by CoffeeScript 1.12.7
(function() {
var CHAT_INIT_PARAMS, CHAT_INIT_URL, CLIENT_CONVERSATION_STATE_LIST, CLIENT_GET_SELF_INFO_RESPONSE, INITIAL_CLIENT_ENTITIES, Init, NetworkError, Q, find, fs, log, ref, ref1, req, request, syspath, uniqfn;
request = require('request');
log = require('bog');
Q = require('q');
fs = require('fs');
syspath = require('path');
ref = require('./util'), req = ref.req, find = ref.find, uniqfn = ref.uniqfn, NetworkError = ref.NetworkError;
ref1 = require('./schema'), CLIENT_GET_SELF_INFO_RESPONSE = ref1.CLIENT_GET_SELF_INFO_RESPONSE, CLIENT_CONVERSATION_STATE_LIST = ref1.CLIENT_CONVERSATION_STATE_LIST, INITIAL_CLIENT_ENTITIES = ref1.INITIAL_CLIENT_ENTITIES;
CHAT_INIT_URL = 'https://hangouts.google.com/webchat/u/0/load';
CHAT_INIT_PARAMS = {
fid: 'gtn-roster-iframe-id',
ec: '["ci:ec",true,true,false]',
pvt: null
};
module.exports = Init = (function() {
function Init(proxy) {
this.proxy = proxy;
}
Init.prototype.initChat = function(jarstore, pvt) {
var opts, params;
params = clone(CHAT_INIT_PARAMS);
params.pvt = pvt;
opts = {
method: 'GET',
uri: CHAT_INIT_URL,
qs: params,
jar: request.jar(jarstore),
proxy: this.proxy
};
return req(opts).then((function(_this) {
return function(res) {
if (res.statusCode === 200) {
return _this.parseBody(res.body);
} else {
log.warn('init failed', res.statusCode, res.statusMessage);
return Q.reject(NetworkError.forRes(res));
}
};
})(this));
};
Init.prototype.parseBody = function(body) {
return Q().then(function() {
var html, out;
html = body.replace(/<!DOCTYPE html><html>(.|\n)*<\/html>/gm, '');
html = html.replace(/<\/?script.*>/gm, '');
html = html.replace('var AF_initDataChunkQueue =', 'var AF_initDataChunkQueue = this.AF_initDataChunkQueue =');
(function() {
return eval(html);
}).bind(out = {})();
return out;
}).then((function(_this) {
return function(out) {
var DICT, d, ent, entgroups, k, spec;
DICT = {
apikey: {
key: 'ds:7',
fn: function(d) {
return d[0][2];
}
},
email: {
key: 'ds:31',
fn: function(d) {
return d[0][2];
}
},
headerdate: {
key: 'ds:2',
fn: function(d) {
return d[0][4];
}
},
headerversion: {
key: 'ds:2',
fn: function(d) {
return d[0][6];
}
},
headerid: {
key: 'ds:4',
fn: function(d) {
return d[0][7];
}
},
timestamp: {
key: 'ds:20',
fn: function(d) {
return new Date(d[0][1][4] / 1000);
}
},
self_entity: {
key: 'ds:20',
fn: function(d) {
return CLIENT_GET_SELF_INFO_RESPONSE.parse(d[0]).self_entity;
}
},
conv_states: {
key: 'ds:20',
fn: function(d) {
return CLIENT_CONVERSATION_STATE_LIST.parse(d[0][3]);
}
}
};
for (k in DICT) {
spec = DICT[k];
ent = find(out.AF_initDataChunkQueue, function(e) {
return spec.key === e.key;
});
if (ent) {
_this[k] = d = spec.fn(ent.data());
if (d.length) {
log.debug('init data count', k, d.length);
} else {
log.debug('init data', k, d);
}
} else {
log.warn('no init data for', k);
}
}
entgroups = [];
return _this.entities = void 0;
};
})(this));
};
return Init;
})();
}).call(this);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment