@proak/fut
Version:
Node implementation of Fifa Ultimate Team
406 lines (360 loc) • 10.3 kB
JavaScript
'use strict'
const { format } = require('url')
const urls = require('./urls')
const { merge } = require('lodash')
const { parse } = require('./utils')
const defaults = require('./defaults')
const request = require('request-promise')
const queryString = require('query-string')
const { CookieJar } = require('tough-cookie')
const requestInternal = request.defaults({
json: true,
gzip: true,
followAllRedirects: true,
headers: {
DNT: '1',
Referer: urls.HOME,
Connection: 'keep-alive',
'Cache-Control': 'max-age=0',
Origin: urls.fut19.EASPORTS,
Accept: defaults.DEFAULT_ACCEPT,
'Accept-Language': 'en-US,en;q=0.9',
'Accept-Encoding': 'gzip, deflate, br',
'User-Agent': defaults.DEFAULT_USER_AGENT
}
})
module.exports = () => {
let cookieJar = request.jar()
const serializeCookieJar = _ => JSON.stringify(cookieJar._jar)
const updateCookieJar = jar => {
if (jar === '') {
return
}
cookieJar = merge(cookieJar, {
_jar: CookieJar.deserializeSync(jar)
})
}
const getLoginFormUrl = async _ => {
const { request, body } = await requestInternal.get({
jar: cookieJar,
resolveWithFullResponse: true,
uri: urls.accounts.AUTH_PROMPT,
headers: {
Referer: urls.HOME,
Host: 'accounts.ea.com',
'Upgrade-Insecure-Requests': '1'
}
})
const title = parse.title(body)
if (title !== 'Log In') {
throw new Error('Unknown Fifa FUT 19 auth prompt response')
}
return request.href
}
const getVerificationUrl = async ({ redirectUrl, referer }) => {
const { body, request } = await requestInternal.get({
jar: cookieJar,
uri: redirectUrl,
headers: {
Referer: referer,
Host: 'signin.ea.com',
Origin: 'https://signin.ea.com',
'Upgrade-Insecure-Requests': '1'
},
resolveWithFullResponse: true
})
const title = parse.title(body)
if (title !== 'Login Verification') {
throw new Error('Unknown Fifa FUT 19 login form verify response title')
}
return request.href
}
const sendVerificationCode = async verificationUrl => {
const { body, request } = await requestInternal.post({
json: true,
jar: cookieJar,
uri: verificationUrl,
headers: {
Referer: verificationUrl,
Host: 'signin.ea.com',
Origin: 'https://signin.ea.com',
'Upgrade-Insecure-Requests': '1'
},
form: {
codeType: 'EMAIL',
_eventId: 'submit'
},
resolveWithFullResponse: true
})
const email = parse.email(body)
return {
email,
verificationUrl: request.href
}
}
const loginWithCredentials = async ({ email, password }) => {
const formUrl = await getLoginFormUrl()
const { body, request } = await requestInternal.post({
uri: formUrl,
jar: cookieJar,
form: {
email,
password,
country: 'GH',
phoneNumber: '',
passwordForPhone: '',
_rememberMe: 'on',
rememberMe: 'on',
_eventId: 'submit',
gCaptchaResponse: '',
isPhoneNumberLogin: 'false',
isIncompletePhone: ''
},
resolveWithFullResponse: true,
headers: {
Referer: formUrl,
Host: 'signin.ea.com',
Origin: 'https://signin.ea.com',
'Content-Type': 'application/x-www-form-urlencoded',
'Upgrade-Insecure-Requests': '1'
}
})
const title = parse.title(body)
if (title === 'Log In') {
throw new Error('Invalid Fifa FUT 19 Login Credentials')
}
const redirectUrl = parse.redirectUrl(body)
return {
redirectUrl,
referer: request.href
}
}
const loginInternal = async ({ email, password }) => {
const { redirectUrl, referer } = await loginWithCredentials({
email,
password
})
console.log('==> after with creds', referer, redirectUrl)
const verificationUrl = await getVerificationUrl({
referer,
redirectUrl
})
console.log('===> verification url', verificationUrl)
const details = await sendVerificationCode(verificationUrl)
console.log('==> send code url', details)
return details
}
const sendSecurityCode = async ({ uri, code = '' }) => {
const { request } = await requestInternal.post({
uri,
jar: cookieJar,
headers: {
Referer: uri,
Origin: 'https://signin.ea.com',
'Upgrade-Insecure-Requests': '1'
},
form: {
oneTimeCode: code,
_eventId: 'submit',
trustThisDevice: 'on',
_trustThisDevice: 'on'
},
resolveWithFullResponse: true
})
const {
token_type: tokenType, access_token: accessToken
} = queryString.parse(request.href.split('#')[1])
if (!(tokenType && accessToken)) {
throw new Error('Unable to retrieve tokens')
}
return {
tokenType,
accessToken
}
}
const getNucleusCreds = async ({ jar = '' } = {}) => {
updateCookieJar(jar)
const body = await requestInternal.get({
jar: cookieJar,
url: urls.accounts.AUTH,
resolveWithFullResponse: false
})
if (!body.access_token || !body.token_type) {
throw new Error('Could not get access tokens')
}
return body
}
const getIdentity = async ({ token, jar = '' } = {}) => {
updateCookieJar(jar)
return requestInternal.get({
jar: cookieJar,
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
Accept: 'application/json; charset=utf-8; */*; q=0.01'
},
resolveWithFullResponse: false,
url: urls.accounts.PROXY_IDENTITY
})
}
const getAccountInfo = async ({ pidId, jar = '' } = {}, platform) => {
updateCookieJar(jar)
const { accounts } = urls
try {
const { userAccountInfo: { personas } } = await requestInternal.get({
url: format({
protocol: 'https',
host: getHost(platform),
pathname: accounts.ACCOUNT_INFO_PATH,
query: {
filterConsoleLogin: true,
sku: 'FUT19WEB',
returningUserGameYear: 2018
}
}),
jar: cookieJar,
resolveWithFullResponse: false,
headers: {
'Content-Type': 'application/json',
'Easw-Session-Data-Nucleus-Id': pidId,
Accept: 'application/json; charset=utf-8; */*; q=0.01'
}
})
return personas[0]
} catch (error) {
console.log('Trying to [FifaFut.getAccountInfo]', error)
const message = (error.error && error.error.reason) ||
`Account not available for platform ${platform}`
throw new Error(message)
}
}
const getAuthCode = async token => {
return requestInternal.get({
json: true,
jar: cookieJar,
resolveWithFullResponse: false,
url: `${urls.accounts.AUTH_CHECK}${token}`,
headers: {
'Content-Type': 'application/json',
Accept: 'application/json; charset=utf-8; */*; q=0.01'
}
})
}
const getGameSKU = platform => {
let suffix = 'PCC'
switch (platform.toLowerCase()) {
case 'xbox':
suffix = 'XBO'
break
case 'xbox360':
suffix = 'XBX'
break
case 'ps3':
suffix = 'PS3'
break
case 'ps4':
suffix = 'PS4'
break
}
return `FFA19${suffix}`
}
const getHost = platform => {
const { XBOX, OTHERS } = urls.hosts
return platform.toLowerCase().includes('xbox') ? XBOX : OTHERS
}
const getSession = async ({ code, personaId, jar = '', platform } = {}) => {
updateCookieJar(jar)
return requestInternal.post({
jar: cookieJar,
url: format({
protocol: 'https',
pathname: urls.accounts.SESSION_AUTH_PATH,
host: getHost(platform),
query: {
sku_c: 'fut19'
}
}),
headers: {
'Content-Type': 'application/json',
Accept: 'application/json; charset=utf-8; */*; q=0.01'
},
body: {
locale: 'en-US',
sku: 'FUT19WEB',
priorityLevel: 4,
clientVersion: 1,
isReadOnly: false,
method: 'authcode',
gameSku: getGameSKU(platform),
nucleusPersonaId: personaId,
identification: {
authCode: code,
redirectUrl: 'nucleus:rest'
}
}
})
}
const getUserMassInfo = async ({ sid }, platform) => {
return requestInternal.get({
jar: cookieJar,
url: format({
protocol: 'https',
host: getHost(platform),
pathname: urls.accounts.USER_MASS_INFO_PATH
}),
headers: {
'X-UT-SID': sid,
'Content-Type': 'application/json',
Accept: 'application/json; charset=utf-8; */*; q=0.01'
}
})
}
const getAccount = async ({ jar = '', platform = 'PS4' } = {}) => {
updateCookieJar(jar)
const { access_token: accessToken } = await getNucleusCreds()
const { pid } = await getIdentity({ token: accessToken })
const accountInfo = await getAccountInfo(pid, platform)
const { code } = await getAuthCode(accessToken)
const session = await getSession({
code,
personaId: accountInfo.personaId,
platform
})
const massInfo = await getUserMassInfo(session, platform)
return {
...massInfo,
accessToken,
sid: session.sid,
pidId: pid.pidId,
cookieJar: serializeCookieJar(),
phishingToken: session.phishingToken
}
}
return {
getAccount,
getIdentity,
getAccountInfo,
getNucleusCreds,
async login (details) {
const data = await loginInternal(details)
return {
...data,
success: true,
cookieJar: serializeCookieJar(),
message: 'Awaiting security code'
}
},
async verifySecurityCode ({ verificationUrl, code, jar = '' }) {
cookieJar = merge(request.jar(), {
_jar: CookieJar.deserializeSync(jar)
})
const tokenDetails = await sendSecurityCode({
code,
uri: verificationUrl
})
return {
cookieJar: serializeCookieJar(), ...tokenDetails
}
}
}
}