UNPKG

fut

Version:
179 lines (151 loc) 6.19 kB
'use strict' import Promise from 'bluebird' import assert from 'assert' import utils from './lib/utils' import Login from './lib/login' import MobileLogin from './lib/mobile-login' import _ from 'underscore' import Methods from './lib/methods' import moment from 'moment' import request from 'request' let Fut = class Fut extends Methods { static isPriceValid = utils.isPriceValid; static calculateValidPrice = utils.calculateValidPrice; static calculateNextLowerPrice = utils.calculateNextLowerPrice; static calculateNextHigherPrice = utils.calculateNextHigherPrice; static getBaseId = utils.getBaseId; /** * [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.tfAuthHandler [description] * @param {Boolean} options.saveVariable [description] * @param {Boolean} options.loadVariable [description] * @param {Number} options.RPM [description] * @param {Number} options.minDelay [description] * @param {[String]} options.proxy [description] * @param {[String]} options.loginType [description] * @param {[Function]} options.preHook [Function that return a promise] * @return {[type]} [description] */ constructor (options) { super() assert(options.email, 'Email is required') assert(options.password, 'Password is required') assert(options.secret, 'Secret is required') assert(options.platform, 'Platform is required') let defaultOptions = { RPM: 0, minDelay: 0, loginType: 'web' } this.options = {} this.isReady = false // instance will be ready after we called _init func Object.assign(this.options, defaultOptions, _.omit(options, 'preHook')) if (_.isFunction(options.preHook)) { this.preHook = options.preHook.bind(this) } if (this.options.loginType === 'web') { this.loginLib = Promise.promisifyAll(new Login({proxy: options.proxy})) } else if (this.options.loginType === 'mobile') { this.loginLib = new MobileLogin({...options, tfCodeHandler: options.tfAuthHandler}) } else { throw new Error(`Unknown loginType ${this.options.loginType}`) } } async loadVariable (key) { if (!this.options.loadVariable) return null return this.options.loadVariable(key) } async saveVariable (key, val) { if (!this.options.saveVariable) return null return this.options.saveVariable(key, val) } async _init () { const cookie = await this.loadVariable('cookie') if (cookie) { this.loginLib.setCookieJarJSON(cookie) } const minuteLimitStartedAt = await this.loadVariable('minuteLimitStartedAt') this.minuteLimitStartedAt = minuteLimitStartedAt || moment() } async login () { await this._init() const loginMethod = this.options.loginType === 'web' ? 'loginAsync' : 'login' const loginResponse = await this.loginLib[loginMethod](this.options.email, this.options.password, this.options.secret, this.options.platform, this.options.tfAuthHandler, this.options.captchaHandler) await this.saveVariable('cookie', this.loginLib.getCookieJarJSON()) this.rawApi = loginResponse.apiRequest const loginDefaults = _.omit(this.loginLib.getLoginDefaults(), 'jar') await this.saveVariable('loginDefaults', loginDefaults) if (this.options.loginType === 'web') this.rawApi = Promise.promisify(this.rawApi, this) this.isReady = true } async loginCached () { const loginDefaults = await this.loadVariable('loginDefaults') if (!loginDefaults) { throw new Error('Login defaults are not saved. Use classic login first!') } let rawApi = request.defaults(loginDefaults) if (this.options.proxy) { rawApi = rawApi.defaults({proxy: this.options.proxy}) } this.rawApi = Promise.promisify(rawApi, this) this.isReady = true } async api (url, options) { if (!this.isReady) throw new Error('Fut instance is not ready yet, run login first!') // limit handler await this._limitHandler() if (this.preHook) await this.preHook() const defaultOptions = { xHttpMethod: 'GET', headers: {} } options = _.extend(defaultOptions, options) options.url = url options.method = 'POST' options.headers['X-HTTP-Method-Override'] = options.xHttpMethod delete options.xHttpMethod const {statusCode, statusMessage, body} = await this.rawApi(options) if (statusCode.toString()[0] !== '2') { const request = {url, options: options} const err = new Error(`FUT api http error: ${statusCode} ${statusMessage} ${JSON.stringify(body)} request was: ${JSON.stringify(request)}`) err.futApiStatusCode = Number(statusCode) throw err } if (utils.isApiError(body)) { body.request = {url, options: options} const err = new Error(`Fut api error: ${JSON.stringify(body)}`) err.futApiStatusCode = Number(body.code) throw err } return body } async _limitHandler () { // seconds const sinceLastRequest = moment().diff(this.lastRequestAt) if (sinceLastRequest < this.options.minDelay) { console.log('Waiting on second limit ...') await Promise.delay(this.options.minDelay - sinceLastRequest) } // minutes if (moment().diff(this.minuteLimitStartedAt, 'minutes') >= 1 || !this.minuteLimitStartedAt) { this.minuteLimitStartedAt = moment() this.requestsThisMinute = 0 } else { this.requestsThisMinute++ } if ((this.requestsThisMinute >= this.options.RPM) && this.options.RPM !== 0) { const resetsAt = this.minuteLimitStartedAt.add(1, 'minute') const needsToReset = resetsAt.diff(moment()) console.log(`Waiting on RPM ... ${needsToReset}`) await Promise.delay(needsToReset) } // TODO: continue this this.lastRequestAt = moment() } } module.exports = Fut