fut
Version:
fifa 17 web-app api
532 lines (432 loc) • 16.9 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
var _bluebird = require('bluebird');
var _bluebird2 = _interopRequireDefault(_bluebird);
var _assign = require('babel-runtime/core-js/object/assign');
var _assign2 = _interopRequireDefault(_assign);
// example EEA58055-E4E8-42E6-B89D-DFFBBD37AF57
let generateMachineKey = (() => {
var _ref6 = (0, _bluebird.coroutine)(function* () {
let parts = yield _bluebird2.default.all([randomHex(8), randomHex(4), randomHex(4), randomHex(4), randomHex(12)]);
return `${ parts[0] }-${ parts[1] }-${ parts[2] }-${ parts[3] }-${ parts[4] }`;
});
return function generateMachineKey() {
return _ref6.apply(this, arguments);
};
})();
let randomHex = (() => {
var _ref7 = (0, _bluebird.coroutine)(function* (length) {
let uppercase = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
let randomHexStr = yield crypto.randomBytesAsync(48);
randomHexStr = randomHexStr.toString('hex').substring(0, length);
if (uppercase) randomHexStr = randomHexStr.toUpperCase();
return randomHexStr;
});
return function randomHex(_x, _x2) {
return _ref7.apply(this, arguments);
};
})();
var _request = require('request');
var _request2 = _interopRequireDefault(_request);
var _cheerio = require('cheerio');
var _cheerio2 = _interopRequireDefault(_cheerio);
var _assert = require('assert');
var _assert2 = _interopRequireDefault(_assert);
var _url = require('url');
var _url2 = _interopRequireDefault(_url);
var _underscore = require('underscore');
var _underscore2 = _interopRequireDefault(_underscore);
var _eaHasher = require('./eaHasher');
var _eaHasher2 = _interopRequireDefault(_eaHasher);
var _toughCookie = require('tough-cookie');
var _lodash = require('lodash');
var _lodash2 = _interopRequireDefault(_lodash);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
const crypto = _bluebird2.default.promisifyAll(require('crypto'));
class MobileLogin {
/**
* [constructor description]
* @param {[type]} options.email [description]
* @param {[type]} options.password [description]
* @param {[type]} options.secret [description]
* @param {[type]} options.platform [description]
* @param {[type]} options.captchaHandler [description]
* @param {[type]} options.tfCodeHandler [description]
* @param {[String]} options.proxy [description]
* @return {[type]} [description]
*/
constructor(options) {
this.jar = _request2.default.jar();
this.loginDefaults = {};
this.setCookieJarJSON = function (json) {
this.jar._jar = _toughCookie.CookieJar.deserializeSync(json);
};
this.getLoginDefaults = function () {
return this.loginDefaults;
};
(0, _assert2.default)(options.email, 'Email is required');
(0, _assert2.default)(options.password, 'Password is required');
(0, _assert2.default)(options.secret, 'Secret is required');
(0, _assert2.default)(options.platform, 'Platform is required');
(0, _assert2.default)(options.tfCodeHandler, 'tfCodeHandler is required');
options.secret = (0, _eaHasher2.default)(options.secret);
const defaultOptions = {
gameSku: getGameSku(options.platform),
platform: getPlatform(options.platform)
};
this.options = {};
(0, _assign2.default)(this.options, defaultOptions, options);
this.initDefaultRequest();
}
initDefaultRequest() {
const requestConfigObj = {
jar: this.jar,
followAllRedirects: true,
gzip: true,
headers: {
'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 10_0_1 like Mac OS X) AppleWebKit/602.1.50 (KHTML, like Gecko) Mobile/14A403',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Encoding': 'gzip, deflate',
'Accept-Language': 'en-US,en;q=0.8'
}
};
if (this.options.proxy) {
requestConfigObj.proxy = this.options.proxy;
}
this.defaultRequest = _bluebird2.default.promisifyAll(_request2.default.defaults(requestConfigObj));
_lodash2.default.merge(this.loginDefaults, requestConfigObj);
}
getCookieJarJSON() {
return this.jar._jar.serializeSync();
}
login() {
var _this = this;
return (0, _bluebird.coroutine)(function* () {
let response = yield _this.getLogin();
yield _this.postLogin(response.request.href);
return { apiRequest: _this.api };
})();
}
getLogin() {
var _this2 = this;
return (0, _bluebird.coroutine)(function* () {
// We will use machine key later at getNucleusCode
_this2.machineKey = yield generateMachineKey();
const url = 'https://accounts.ea.com/connect/auth?client_id=FIFA-17-MOBILE-COMPANION&response_type=code&display=web2/login&scope=basic.identity+offline+signin&locale=en_US&prompt=login&machineProfileKey=' + _this2.machineKey;
const response = yield _this2.defaultRequest.getAsync(url);
const title = getTitle(response);
if (title !== 'Log In') {
throw new Error(`Unknown response at 'getLogin' title was: ${ title }`);
}
return response;
})();
}
postLogin(url) {
var _this3 = this;
return (0, _bluebird.coroutine)(function* () {
const form = {
email: _this3.options.email,
password: _this3.options.password,
country: 'HU',
phoneNumber: '',
passwordForPhone: '',
_rememberMe: 'on',
rememberMe: 'on',
_eventId: 'submit',
gCaptchaResponse: '',
isPhoneNumberLogin: false,
isIncompletePhone: ''
};
const response = yield _this3.defaultRequest.postAsync(url, { form: form });
const title = getTitle(response);
if (title === 'Log In') throw new Error('Unable to login. Wrong email or password ?');
if (!response.body.includes('redirectUri')) {
throw new Error(`Unknow response at 'postLogin' title was: ${ title }`);
}
return _this3.postLoginRedirect(response);
})();
}
postLoginRedirect(prevResponse) {
var _this4 = this;
return (0, _bluebird.coroutine)(function* () {
const urlRegex = new RegExp("var redirectUri = '(.*)'");
const qsRegex = /redirectUri = redirectUri \+ "(.*)";/;
let nextUrl;
try {
nextUrl = urlRegex.exec(prevResponse.body)[1];
nextUrl += qsRegex.exec(prevResponse.body)[1];
} catch (e) {
throw new Error(`RegExp failed at 'postLogin' body was: ${ prevResponse.body }`);
}
const response = yield _this4.defaultRequest.getAsync(nextUrl);
const title = getTitle(response);
if (title === 'Login Verification') return _this4.handleTwoFactorCode(response.request.href);else if (response.request.href.includes('code=')) {
try {
let code = _url2.default.parse(response.request.href, true).query.code;
return _this4.wtfLogin(code);
} catch (e) {
throw new Error(`Couldn't parse code from url at postLoginRedirect: ${ e.message }`);
}
}
throw Error(`Unknow response at 'postLoginRedirect' title was: ${ title }`);
})();
}
handleTwoFactorCode(url) {
var _this5 = this;
return (0, _bluebird.coroutine)(function* () {
const tfCode = yield _this5.options.tfCodeHandler();
const form = {
twofactorCode: tfCode,
trustThisDevice: 'on',
_eventId: 'submit'
};
const response = yield _this5.defaultRequest.postAsync(url, { form: form });
const title = getTitle(response);
if (title === 'Set Up an App Authenticator') return _this5.cancelLoginVerificationUpdate(response.request.href);
if (title === 'Login Verification') throw new Error('Wrong two factor code.');
throw Error(`Unknow response at 'handleTwoFactorCode' title was: ${ title }`);
})();
}
cancelLoginVerificationUpdate(url) {
var _this6 = this;
return (0, _bluebird.coroutine)(function* () {
let code;
yield _this6.defaultRequest.postAsync(url, {
form: {
'_eventId': 'cancel',
'appDevice': 'IPHONE'
},
followRedirect: function followRedirect(response) {
if (response.headers.location.includes('code=')) {
try {
code = _url2.default.parse(response.headers.location, true).query.code;
} catch (e) {
throw new Error(`Couldn't parse code from headers at 'cancelLoginVerificationUpdate' original error: ${ e.message }`);
}
return false;
}
return true;
}
});
return _this6.wtfLogin(code);
})();
}
wtfLogin(code) {
var _this7 = this;
return (0, _bluebird.coroutine)(function* () {
const postUrl = `https://accounts.ea.com/connect/token?grant_type=authorization_code&code=${ code }&client_id=FIFA-17-MOBILE-COMPANION&client_secret=qIdl15XHu4VWrcolro37Um0JiuRjnbIspnYtXmv3zr6pPL9S0N9H1IutSFJvnCORH3isebmeVdCtHaFC`;
let response = yield _this7.defaultRequest.postAsync(postUrl, { json: true, headers: { 'content-type': 'application/x-www-form-urlencoded' } });
let token = response.body.access_token;
(0, _assert2.default)(token, 'Failed to get access token at `wtfLogin`');
// this stuff seems useless but let's just do it
const url1 = `https://signin.ea.com/p/mobile/fifa/companion/code?code=${ code }`;
yield _this7.defaultRequest.getAsync(url1);
const nucleusUserId = yield _this7.getPid(token);
// const sidCode = await this.getSidCode(token)
const sidCode2 = yield _this7.getSidCode(token);
// We will get the api url after shards
yield _this7.getShards();
const nucleusPersonaId = yield _this7.getUserAccounts(nucleusUserId);
// const powSessionId = await this.getPowSid(sidCode)
// const nucleusPersonaId = await this.getNucleusPersonaId(nucleusUserId, powSessionId)
const sid = yield _this7.getSid(sidCode2, nucleusPersonaId);
const requestConfigObj1 = {
baseUrl: `${ _this7.apiUrl }/`,
json: true,
headers: {
'X-UT-SID': sid,
'Easw-Session-Data-Nucleus-Id': nucleusUserId
}
};
_lodash2.default.merge(_this7.loginDefaults, requestConfigObj1);
_this7.api = _bluebird2.default.promisifyAll(_this7.defaultRequest.defaults(requestConfigObj1));
const phisingToken = yield _this7.validate();
const requestConfigObj2 = {
headers: {
'X-UT-PHISHING-TOKEN': phisingToken,
'X-HTTP-Method-Override': 'GET'
}
};
_lodash2.default.merge(_this7.loginDefaults, requestConfigObj2);
const finalApi = _this7.api.defaults(requestConfigObj2);
_this7.api = _bluebird2.default.promisify(finalApi);
return _this7.api;
})();
}
getSidCode(token) {
var _this8 = this;
return (0, _bluebird.coroutine)(function* () {
// https://accounts.ea.com/connect/auth?client_id=FOS-SERVER&redirect_uri=nucleus:rest&response_type=code&access_token=QVQxOjEuMDozLjA6NjA6b3lXeGg1dXFSd2t0VGVPcGFoaVlzMW1pRVhyZ1ZOT3F0UWo6MTYzMzg6bmRxYTQ&machineProfileKey=EEA58055-E4E8-42E6-B89D-DFFBBD37AF57
const url = `https://accounts.ea.com/connect/auth?client_id=FOS-SERVER&redirect_uri=nucleus:rest&response_type=code&access_token=${ token }&machineProfileKey=${ _this8.machineKey }`;
var _ref = yield _this8.defaultRequest.getAsync(url, { json: true });
const body = _ref.body;
return body.code;
})();
}
getPid(token) {
var _this9 = this;
return (0, _bluebird.coroutine)(function* () {
const url = 'https://gateway.ea.com/proxy/identity/pids/me';
const response = yield _this9.defaultRequest.getAsync(url, { json: true, headers: {
Authorization: `Bearer ${ token }`,
Accept: '*/*'
} });
return response.body.pid.externalRefValue;
})();
}
getShards() {
var _this10 = this;
return (0, _bluebird.coroutine)(function* () {
// https://utas.mob.v5.fut.ea.com/ut/shards/v2?_=1474137502721
const timestamp = new Date().getTime();
const url = `https://utas.mob.v5.fut.ea.com/ut/shards/v2?_=${ timestamp }`;
var _ref2 = yield _this10.defaultRequest.getAsync(url, {
json: true,
headers: {
'Easw-Session-Data-Nucleus-Id': _this10.nucleus
}
});
const body = _ref2.body;
const shard = _underscore2.default.find(body.shardInfo, function (shard) {
return shard.skus.includes(_this10.options.gameSku);
});
_this10.apiUrl = 'https://' + shard.clientFacingIpPort.slice(0, -4);
})();
}
getUserAccounts(nucleusUserId) {
var _this11 = this;
return (0, _bluebird.coroutine)(function* () {
const timestamp = new Date().getTime();
const url = `${ _this11.apiUrl }/ut/game/fifa17/user/accountinfo?sku=FUT17IOS&_=${ timestamp }`;
var _ref3 = yield _this11.defaultRequest.getAsync(url, { json: true, headers: {
'Easw-Session-Data-Nucleus-Id': nucleusUserId,
'X-UT-SID': ''
} });
const body = _ref3.body;
return body.userAccountInfo.personas[0].personaId;
})();
}
getNucleusPersonaId(nucleusUserId, powSessionId) {
var _this12 = this;
return (0, _bluebird.coroutine)(function* () {
const timestamp = new Date().getTime();
const url = `https://pas.mob.v5.easfc.ea.com:8095/pow/user/self/tiergp/NucleusId/tiertp/${ nucleusUserId }?offset=0&count=50&_=${ timestamp }`;
var _ref4 = yield _this12.defaultRequest.getAsync(url, { json: true, headers: {
'Easw-Session-Data-Nucleus-Id': nucleusUserId,
'X-POW-SID': powSessionId
} });
const body = _ref4.body;
return _underscore2.default.findWhere(body.userData.data, { sku: _this12.options.gameSku }).nucPersId;
})();
}
getSid(code, nucleusPersonaId) {
var _this13 = this;
return (0, _bluebird.coroutine)(function* () {
const requestBody = {
isReadOnly: false,
sku: 'FUT17IOS',
clientVersion: 21,
locale: 'en-US',
method: 'authcode',
priorityLevel: 4,
identification: {
authCode: code,
redirectUrl: 'nucleus:rest'
},
nucleusPersonaId: nucleusPersonaId,
gameSku: _this13.options.gameSku
};
// 1474229595686
const timestamp = new Date().getTime();
const url = `${ _this13.apiUrl }/ut/auth?timestamp=${ timestamp }`;
const response = yield _this13.defaultRequest.postAsync(url, {
body: requestBody,
json: true,
headers: {
'X-UT-SID': '',
'X-POW-SID': '',
Accept: 'text/plain, */*; q=0.01',
Origin: 'file://'
}
});
return response.body.sid;
})();
}
getPowSid(code) {
var _this14 = this;
return (0, _bluebird.coroutine)(function* () {
const requestBody = {
isReadOnly: true,
sku: 'FUT17IOS',
clientVersion: 20,
locale: 'en-US',
method: 'authcode',
priorityLevel: 4,
identification: {
authCode: code,
redirectUrl: 'nucleus:rest'
}
};
// 1474229595686
const timestamp = new Date().getTime();
const url = `https://pas.mob.v5.easfc.ea.com:8095/pow/auth?timestamp=${ timestamp }`;
const response = yield _this14.defaultRequest.postAsync(url, {
body: requestBody,
json: true
// headers: {
// 'X-UT-SID': '',
// 'X-POW-SID': '',
// Accept: 'text/plain, */*; q=0.01',
// Origin: 'file://'
// }
});
return response.body.sid;
// return powSid
})();
}
validate() {
var _this15 = this;
return (0, _bluebird.coroutine)(function* () {
const uri = `/ut/game/fifa17/phishing/validate?answer=${ _this15.options.secret }`;
var _ref5 = yield _this15.api.postAsync(uri, { body: _this15.options.secret });
const body = _ref5.body;
return body.token;
})();
}
}
exports.default = MobileLogin;
function getTitle(response) {
const $ = _cheerio2.default.load(response.body);
const title = $('title').text();
return title;
}
function getGameSku(platform) {
switch (platform) {
case 'pc':
return 'FFA17PCC';
case 'ps3':
return 'FFA17PS3';
case 'ps4':
return 'FFA17PS4';
case 'x360':
return 'FFA17XBX';
case 'xone':
return 'FFA17XBO';
}
return null;
}
function getPlatform(platform) {
switch (platform) {
case 'pc':
return 'pc';
case 'ps3':
case 'ps4':
return 'ps3';
case 'x360':
case 'xone':
return '360';
}
return null;
}