UNPKG

fut

Version:
532 lines (432 loc) 16.9 kB
'use strict'; 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; }