NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript== // @name FUT Autobuyer // @version 0.1.2 // @description Automatically search and buy items matching search criteria, currenly based on max buy now value // @license MIT // @author Tim Klingeleers // @match https://www.easports.com/fifa/ultimate-team/web-app/* // @match https://www.easports.com/*/fifa/ultimate-team/web-app/* // @namespace https://github.com/Mardaneus86 // @updateURL https://raw.githubusercontent.com/Mardaneus86/futwebapp-tampermonkey/master/autobuyer.user.js // @downloadURL https://raw.githubusercontent.com/Mardaneus86/futwebapp-tampermonkey/master/autobuyer.user.js // @supportURL https://github.com/Mardaneus86/futwebapp-tampermonkey/issues // @grant GM_setValue // @grant GM_getValue // @grant GM_notification // @grant window.focus // ==/UserScript== // ==OpenUserJS== // @author Mardaneus86 // ==/OpenUserJS== (function () { 'use strict'; var delayHour = 1; GM_setValue('log', ''); // clear the log on restart GM_setValue('isSearching', false); var addMessage = function addMessage(msg) { var oldLog = GM_getValue('log', ''); var message = "[" + new Date() + "] " + msg + "\n"; GM_setValue('log', oldLog + message); $('#progressAutobuyer').val(oldLog + message); scrollLogToBottom(); }; var scrollLogToBottom = function scrollLogToBottom() { var log = $('#progressAutobuyer'); if (log.length) { log.scrollTop(log[0].scrollHeight - log.height()); } }; var isNotExactTraining = function isExactTraining(crit) { if (crit.category === enums.SearchCategory.POSITION) { return crit.position === "any"; } else if (crit.category === enums.SearchCategory.PLAYSTYLE) { return crit.playStyle === -1; } else { return true; // can't search specific enough on the rest of the categories } } // page pages.Autobuyer = function () { pages.Search.call(this); this.updateHeader(components.Header.DEFAULT_CHILD_PAGE, "Auto buyer"); this.$_root.find('.search-container').css('width', '50%'); var log = GM_getValue('log', ''); this.$_root.append('<article class="SearchWrapper" style="width: 50%; left: 50%"><textarea id="progressAutobuyer" style="width: 100%;height: 98%;">' + log + '</textarea></article>'); scrollLogToBottom(); }; utils.JS.inherits(pages.Autobuyer, pages.Search); // controller pages.controllers.AutobuyerController = function (view) { pages.controllers.SearchController.call(this, view); this._viewmodel = new viewmodels.BucketedItemSearch(); this._isSearching = GM_getValue('isSearching', false); }; utils.JS.inherits(pages.controllers.AutobuyerController, pages.controllers.SearchController); pages.controllers.AutobuyerController.prototype._isSearching = false; pages.controllers.AutobuyerController.prototype.onScreenStarted = function () { Object.getPrototypeOf(this.constructor.prototype).onScreenStarted.call(this); this._view._searchButton1.$_text.text(this._isSearching ? "Cancel" : "BIN snipe"); }; pages.controllers.AutobuyerController.prototype.onSearchButtonClicked = function () { if (this._viewmodel.searchCriteria.maxBuy === 0) { addMessage("Can't BIN snipe without a max buy now value"); return; } console.log(this._viewmodel.searchCriteria) if (this._viewmodel.searchCriteria.type === "player" && !this._viewmodel.searchCriteria.maskedDefId || this._viewmodel.searchCriteria.type === "training" && isNotExactTraining(this._viewmodel.searchCriteria) || this._viewmodel.searchCriteria.type === "clubInfo" && (this._viewmodel.searchCriteria.club === -1 && this._viewmodel.searchCriteria.league === -1) || this._viewmodel.searchCriteria.type === "staff" && this._viewmodel.searchCriteria.category === "any") { addMessage("Can't BIN snipe without a specific item"); return; } this._isSearching = !this._isSearching; if (this._isSearching) { this._view._searchButton1.$_text.text("Cancel"); } else { addMessage('Cancelled search'); GM_setValue('searchStartDate', null); clearTimeout(GM_getValue('searchTimeoutHandle')); this._view._searchButton1.$_text.text('BIN snipe'); } GM_setValue('isSearching', this._isSearching); var startSearch = function startSearch(searchCriteria, controller) { // TODO: need a better way to do RPM limiting var delay = 0; var runningTime = new Date().getTime() - GM_getValue('searchStartDate'); if (runningTime > 1 * 60 * 60 * 1000) { // if search has been started more than 1 hours ago addMessage('Running for 1h, waiting for an hour to prevent Captcha'); GM_getValue('searchStartDate', new Date()); delay = delayHour * 60 * 60 * 1000; // wait an hour } else { // randomly wait between 10 and 30 seconds delay = (Math.floor(Math.random() * 20) + 10) * 1000; } GM_setValue('searchTimeoutHandle', setTimeout(function () { var o = new communication.SearchAuctionDelegate(searchCriteria); o.addListener(communication.BaseDelegate.SUCCESS, this, function marketSearch(sender, response) { var rareItems = response.auctionInfo; if (rareItems && rareItems.length > 0) { rareItems.forEach(function (i, index) { var bid = new communication.BidDelegate(i.tradeId, i.buyNowPrice); bid.addListener(communication.BaseDelegate.SUCCESS, this, function _bidSuccess(sender, response) { addMessage(i.tradeId + ' bought for ' + i.buyNowPrice); GM_notification({ text: "Item bought for " + i.buyNowPrice, title: "FUT 18 Web App", onclick: function () { window.focus(); }, }); }); bid.addListener(communication.BaseDelegate.FAIL, this, function _bidFailure(sender, response) { addMessage('Failed to buy ' + i.tradeId + ' for ' + i.buyNowPrice); }); bid.send(); }); } else { addMessage('No items found on market, retrying...'); } startSearch(searchCriteria, controller); }); o.addListener(communication.BaseDelegate.FAIL, this, function marketSearchFailure(sender, response) { var reason = JSON.stringify(response); if (reason._code === enums.HTTPStatusCode.RATE_LIMIT) { addMessage('Rate limit reached while searching, cancelling'); } else if ( reason._code === enums.HTTPStatusCode.NO_INTERNET_CONNECTION || reason._code === enums.HTTPStatusCode.NETWORK_ERROR || reason._code === enums.HTTPStatusCode.REQUEST_TIME_OUT ) { addMessage('Network error, retrying...'); startSearch(searchCriteria, controller); } else if ( reason._code === enums.HTTPStatusCode.FUN_CAPTCHA_REQUIRED || reason._code === enums.HTTPStatusCode.CAPTCHA_REQUIRED) { addMessage('Captcha is required, cancelling'); } else { addMessage('Something went wrong while searching, cancelling: ' + JSON.stringify(response)); } }); o.send(); }, delay) ); }; if (this._isSearching) { var t = gAuthenticationModel.getUser().marketSearchCriteria; t.update(this._viewmodel.searchCriteria); t.maskedDefId = 0; GM_setValue('searchStartDate', new Date().getTime()); var searchCriteria = this._viewmodel.searchCriteria; addMessage("Starting search"); startSearch(searchCriteria, this); } }; Screens.Register('AUTOBUYER', 'Autobuyer', "AUTO_BUYER"); // TODO: find a cleaner way to add new navigation buttons in the sidebar gFooter._btnAutobuyer = new components.FooterButton(); gFooter._btnAutobuyer.getRootElement().classList.add("btnTransfers"); gFooter.__root.appendChild(gFooter._btnAutobuyer.getRootElement()); gFooter._btnAutobuyer.init(); gFooter._btnAutobuyer.setText("#RipDcg"); gFooter._btnAutobuyer.addTarget(gFooter, function () { gNavManager.requestRootScreen(Screens.getView('AUTOBUYER')); gFooter._btnAutobuyer.addClass(enums.UIState.SELECTED); }, enums.Event.TAP); // Override default selection logic for side navigation bar components.Footer.prototype._onScreenRequested = function _onScreenRequested(t, i, s, o) { return s === NavManager.SCREEN_TYPE.ROOT ? (this._btnHome.toggleClass(enums.UIState.SELECTED, i === Screens.getView("HOME")), this._btnSquads.toggleClass(enums.UIState.SELECTED, i === Screens.getView("SQUADS_HUB")), this._btnSBC.toggleClass(enums.UIState.SELECTED, i === Screens.getView("SBC_HUB")), this._btnTransfers.toggleClass(enums.UIState.SELECTED, i === Screens.getView("TRANSFERS_HUB")), this._btnStore.toggleClass(enums.UIState.SELECTED, i === Screens.getView("STORE_HUB")), this._btnClub.toggleClass(enums.UIState.SELECTED, i === Screens.getView("CLUB_HUB")), this._btnSettings.toggleClass(enums.UIState.SELECTED, i === Screens.getView("APP_SETTINGS")), this._btnAutobuyer.toggleClass(enums.UIState.SELECTED, i === Screens.getView("AUTOBUYER"))) : o !== Screens.APP_SECTION.SETTINGS; }; })();