UNPKG

fut

Version:
463 lines (371 loc) 14.9 kB
'use strict' module.exports = function (options) { var __ = require('underscore') var request = require('request') var hasher = require('./eaHasher') var urls = require('./urls')() var CookieJar = require('tough-cookie').CookieJar var utils = require('./utils') const lodash = require('lodash') var defaultRequest = null var loginDetails = {} var loginResponse = { nucleusId: null, shardInfos: null, shard: null, persona: null, sessionData: null, apiRequest: null } var jar = request.jar() var loginDefaults = {} var Login = function () {} Login.prototype.login = function (email, password, secret, platform, tfCodeCb, captchaCb, loginCb, version = 17) { Login.version = version if (!email || !__.isString(email) || email.trim().length <= 0) return loginCb(new Error('Email is empty.')) if (!password || !__.isString(password) || password.trim().length <= 0) return loginCb(new Error('Password is empty.')) if (!secret || !__.isString(secret) || email.trim().length <= 0) return loginCb(new Error('Secret is empty.')) if (!platform || !__.isString(platform) || platform.trim().length <= 0) return loginCb(new Error('Platform is empty.')) if (!getPlatform(platform)) return loginCb(new Error('Platform is invalid.')) if (!__.isFunction(tfCodeCb)) return loginCb(new Error('tfCodeCb is not a function.')) if (!__.isFunction(captchaCb)) return loginCb(new Error('captchaCb is not a function.')) if (!__.isFunction(loginCb)) return loginCb(new Error('loginCb is not a function.')) loginDetails = { 'email': email, 'password': password, 'secret': hasher(secret), 'tfCodeCb': tfCodeCb, 'loginCb': loginCb, 'gameSku': getGameSku(platform, version), 'platform': getPlatform(platform), captchaCb: captchaCb } let requestConfigObj = { jar: jar, followAllRedirects: true, gzip: true, headers: { 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.116 Safari/537.36', 'Accept': 'text/html, application/xhtml+xml, */*', 'Accept-Encoding': 'gzip, deflate', 'Accept-Language': 'en-US,en;q=0.8', 'Connection': 'keep-alive', 'DNT': '1', 'Cache-Control': 'no-cache' } } if (options.proxy) { requestConfigObj.proxy = options.proxy } defaultRequest = request.defaults(requestConfigObj) lodash.merge(loginDefaults, requestConfigObj) getMain() } Login.prototype.getCookieJarJSON = function () { return jar._jar.serializeSync() } Login.prototype.setCookieJarJSON = function (json) { jar._jar = CookieJar.deserializeSync(json) } Login.prototype.getLoginDefaults = function () { return loginDefaults } Login.prototype.getCaptcha = function (cb) { const url = urls.login.getCaptcha + new Date().getTime() defaultRequest.get(url, function (error, response, body) { if (error) return cb(error) if (response.statusCode !== 200) return cb(new Error(`Captcha error ${response.statusCode} ${body}`)) cb(body) }) } function getGameSku (platform, version) { switch (platform) { case 'pc': return `FFA${version}PCC` case 'ps3': return `FFA${version}PS3` case 'ps4': return `FFA${version}PS4` case 'x360': return `FFA${version}XBX` case 'xone': return `FFA${version}XBO` } 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 } function getMain () { defaultRequest.get(urls.login.main, function (error, response, body) { if (error) return loginDetails.loginCb(error) if (body.indexOf('<title>FIFA Football | Football Club | EA SPORTS</title>') > 0) return getNucleus() if (body.indexOf('<title>FIFA Football | FUT Web App | EA SPORTS</title>') > 0) return getNucleus() if (body.indexOf('<title>Log In</title>') > 0) return loginForm(response.request.href) loginDetails.loginCb(new Error('Unknown response. Unable to login.')) }) } function loginForm (url) { defaultRequest.post(url, { form: { 'email': loginDetails.email, 'password': loginDetails.password, '_rememberMe': 'on', 'rememberMe': 'on', '_eventId': 'submit', 'facebookAuth': '' } }, function (error, response, body) { if (error) return loginDetails.loginCb(error) if (body.indexOf('<title>FIFA Football | FUT Web App | EA SPORTS</title>') > 0) return getNucleus() if (body.indexOf('<title>Log In</title>') > 0) return loginDetails.loginCb(new Error('Unable to log in.')) if (body.indexOf('<title>Set Up an App Authenticator</title>') > 0) return cancelLoginVerificationUpdate(response.request.href) if (body.indexOf('<title>Account Update</title>') > 0) return acceptAccountUpdate(response.request.href) if (body.indexOf('<title>Login Verification</title>') > 0) { loginDetails.tfCodeCb().then((tfCode) => sendTwoFactorCode(response.request.href, tfCode)) return } loginDetails.loginCb(new Error('Unknown response. Unable to login.')) }) } function sendTwoFactorCode (url, tfCode) { defaultRequest.post(url, { form: { 'twofactorCode': tfCode, 'twoFactorCode': tfCode, '_eventId': 'submit', '_trustThisDevice': 'on', 'trustThisDevice': 'on' } }, function (error, response, body) { if (error) return loginDetails.loginCb(error) if (body.indexOf('<title>FIFA Football | FUT Web App | EA SPORTS</title>') > 0) return getNucleus() if (body.indexOf('<title>Set Up an App Authenticator</title>') > 0) return cancelLoginVerificationUpdate(response.request.href) if (body.indexOf('<title>Login Verification</title>') > 0) return loginDetails.loginCb(new Error('Wrong two factor code.')) loginDetails.loginCb(new Error('Unknown response. Unable to login.')) }) } function acceptAccountUpdate (url) { defaultRequest.post(url, { form: { '_eventId': 'submit' } }, function (error, response, body) { if (error) return loginDetails.loginCb(error) if (body.indexOf('<title>Login Verification</title>') > 0) return chooseEmailSending(response.request.href) loginDetails.loginCb(new Error('Unknown response. Unable to login. At acceptAccountUpdate')) }) } function chooseEmailSending (url) { defaultRequest.post(url, { form: { tfa_type: '', twofactorType: 'EMAIL', country: 0, phoneNumber: '', _eventId: 'submit', appleDevice: 'IPHONE' } }, function (error, response, body) { if (error) return loginDetails.loginCb(error) if (body.indexOf('<title>Login Verification</title>') > 0) { loginDetails.tfCodeCb().then((tfCode) => sendTwoFactorCode(response.request.href, tfCode)) return } loginDetails.loginCb(new Error('Unknown response. Unable to login. At chooseEmailSending')) }) } function cancelLoginVerificationUpdate (url) { defaultRequest.post(url, { form: { '_eventId': 'cancel', 'appDevice': 'IPHONE' } }, function (error, response, body) { if (error) return loginDetails.loginCb(error) if (body.indexOf('<title>FIFA Football | FUT Web App | EA SPORTS</title>') > 0) return getNucleus() loginDetails.loginCb(new Error('Unknown response. Unable to login.')) }) } function getNucleus () { defaultRequest.get(urls.login.nucleus, function (error, response, body) { if (error) return loginDetails.loginCb(error) var match = body.match(/EASW_ID\W*=\W*'(\d*)'/) if (match === null || match[1] === null) return loginDetails.loginCb(new Error("Unable to get the 'EASW_ID'. Unable to login.")) loginResponse.nucleusId = match[1] getShards() }) } function getShards () { let requestConfigObj = { json: true, headers: { 'Easw-Session-Data-Nucleus-Id': loginResponse.nucleusId, 'X-UT-Embed-Error': true, 'X-UT-Route': 'https://utas.external.fut.ea.com', 'X-Requested-With': 'XMLHttpRequest', 'Referer': urls.referer } } defaultRequest = defaultRequest.defaults(requestConfigObj) lodash.merge(loginDefaults, requestConfigObj) defaultRequest.get(urls.login.shards, function (error, responsse, body) { if (error) return loginDetails.loginCb(error) if (!body || !body.shardInfo) return loginDetails.loginCb(new Error('Unable to get shards. Unable to login.')) loginResponse.shardInfos = body.shardInfo getAccount() }) } function getAccount () { loginResponse.shard = __.find(loginResponse.shardInfos, function (si) { return si.skus.indexOf(loginDetails.gameSku) >= 0 }) if (!loginResponse.shard) return loginDetails.loginCb(new Error('Unable to find shardInfo.')) let requestConfigObj = { headers: { 'X-UT-Route': 'https://' + loginResponse.shard.clientFacingIpPort } } defaultRequest = defaultRequest.defaults(requestConfigObj) lodash.merge(loginDefaults, requestConfigObj) defaultRequest.get(urls.login.accounts, function (error, response, body) { if (error) return loginDetails.loginCb(error) if (!body.userAccountInfo) return loginDetails.loginCb(new Error('Unable to get account infos.')) loginResponse.persona = __.find(body.userAccountInfo.personas, function (persona) { return __.some(persona.userClubList, function (userClub) { return userClub.platform === loginDetails.platform }) }) if (!loginResponse.persona) return loginDetails.loginCb(new Error('Unable to get account info persona.')) getSession() }) } function getSession () { var data = { 'isReadOnly': false, 'sku': `FUT${Login.version}WEB`, 'clientVersion': 1, 'nucleusPersonaId': loginResponse.persona.personaId, 'nucleusPersonaDisplayName': loginResponse.persona.personaName, 'gameSku': loginDetails.gameSku, 'nucleusPersonaPlatform': getPlatform(loginDetails.platform), 'locale': 'en-GB', 'method': 'authcode', 'priorityLevel': 4, 'identification': {'authCode': ''} } defaultRequest.post(urls.login.session, { body: data }, function (error, response, body) { if (error) return loginDetails.loginCb(error) if (response.statusCode !== 200) return loginDetails.loginCb(new Error(`Unknown response. Unable to login. ${response.statusCode} ${JSON.stringify(body)}`)) loginResponse.sessionData = body if (loginResponse.sessionData.sid) return phishing() loginDetails.loginCb(new Error(`Unknown response. Unable to login. ${response.statusCode} ${JSON.stringify(body)}`)) }) } function phishing () { let requestConfigObj = { headers: { 'X-UT-SID': loginResponse.sessionData.sid } } defaultRequest = defaultRequest.defaults(requestConfigObj) lodash.merge(loginDefaults, requestConfigObj) defaultRequest.get(urls.login.question, function (error, response, body) { if (error) return loginDetails.loginCb(error) if (utils.isApiMessage(body) && body.token) { loginResponse.token = body.token let requestConfigObj = { baseUrl: 'https://' + loginResponse.sessionData.ipPort.split(':')[0], headers: { 'X-UT-PHISHING-TOKEN': loginResponse.token, 'X-HTTP-Method-Override': 'GET', 'X-UT-Route': 'https://' + loginResponse.sessionData.ipPort.split(':')[0], 'x-flash-version': '20,0,0,272' } } loginResponse.apiRequest = defaultRequest.defaults(requestConfigObj) lodash.merge(loginDefaults, requestConfigObj) return loginDetails.loginCb(null, loginResponse) } if (body.question) return validate() else if (body.code === '459') { // validate captcha // then do the phishing() again getCaptcha((err, rawCaptcha) => { if (err) loginDetails.loginCb(err) loginDetails.captchaCb(rawCaptcha, (captcha) => { validateCaptcha(captcha, (err) => { if (err) loginDetails.loginCb(err) return phishing() }) }) }) } else { let error = new Error(`Unknown response. Unable to login. response: ${JSON.stringify(body)}`) error.futLoginError = body loginDetails.loginCb(error) } }) } function getCaptcha (cb) { const url = urls.login.captchaImg + new Date().getTime() defaultRequest.get({ url, encoding: null }, function (error, response, body) { if (error) return cb(error) if (response.statusCode !== 200) return cb(new Error(`Captcha error ${response.statusCode} ${body}`)) cb(null, body) }) } function validateCaptcha (captcha, cb) { const url = urls.login.validateCaptcha defaultRequest.post(url, { json: true, body: { 'token': 'AAAA', 'answer': captcha } }, (error, response, body) => { if (error) return cb(error) if (response.statusCode !== 200) return cb(new Error(`Captcha is not ok ${response.statusCode} ${body}`)) cb() }) } function validate () { defaultRequest.post(urls.login.validate, { form: { answer: loginDetails.secret } }, function (error, response, body) { if (error) return loginDetails.loginCb(error) if (!body) return loginDetails.loginCb(new Error('Unknown response. Unable to login.')) if (body.string !== 'OK') return loginDetails.loginCb(new Error('Wrong secret. Unable to login.')) if (body.string === 'OK') loginResponse.token = body.token if (!loginResponse.token) return loginDetails.loginCb(new Error('Unknown response. Unable to login.')) let requestConfigObj = { baseUrl: 'https://' + loginResponse.sessionData.ipPort.split(':')[0], headers: { 'X-UT-PHISHING-TOKEN': loginResponse.token, 'X-HTTP-Method-Override': 'GET', 'X-UT-Route': 'https://' + loginResponse.sessionData.ipPort.split(':')[0], 'x-flash-version': '20,0,0,272', 'Accept': 'application/json' } } loginResponse.apiRequest = defaultRequest.defaults(requestConfigObj) lodash.merge(loginDefaults, requestConfigObj) loginDetails.loginCb(null, loginResponse) }) } return new Login() }