Skip to content

Instantly share code, notes, and snippets.

@NenadGajic
Last active December 5, 2022 17:31
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save NenadGajic/d057c3664dfd305a27989bbd2b31dc83 to your computer and use it in GitHub Desktop.
Save NenadGajic/d057c3664dfd305a27989bbd2b31dc83 to your computer and use it in GitHub Desktop.
FUT
// ==UserScript==
// @name FUT Sniper
// @version 0.8
// @description Adds keyboard shortcuts to FUT Web App
// @license MIT
// @author Nenad Gajic
// @match https://www.easports.com/fifa/ultimate-team/web-app/*
// @match https://www.easports.com/*/fifa/ultimate-team/web-app/*
// @match https://www.ea.com/*/fifa/ultimate-team/web-app/*
// @match https://www.ea.com/fifa/ultimate-team/web-app/*
// @namespace https://github.com/NenadGajic
// ==/UserScript==
const config = {
key: {
searchMarket: 'ArrowRight',
goBack: 'ArrowLeft',
up: 'ArrowUp',
down: 'ArrowDown',
incrementBid: 'IntlBackslash',
decrementBid: 'ShiftLeft',
},
lowerMinimumBid: 'clear', // options: 'clear', 'decrement', 150
autobuy: {
enabled: true,
buyNowTimes: 5,
},
noPlayerWarning: false,
instantBuyNow: true,
language: 'de', // de, en
}
// -------- do not edit the code below ---------- //
const APP_NAME = "FUT Sniper";
const DEBUG_MODE = true;
window.addEventListener('keydown', (event) => {
switch (event.code) {
case config.key.searchMarket:
searchMarket();
config.autobuy.enabled ? autoBuy() : buyNow();
break;
case config.key.goBack:
goBack();
break;
case config.key.up:
move(event);
break;
case config.key.down:
move(event);
break;
case config.key.decrementBid:
if (config.lowerMinimumBid === 'clear') clearBidPrice();
if (config.lowerMinimumBid === 'decrement') editBidPrice('decrement');
if (config.lowerMinimumBid === 150) setBidPrice(150);
break;
case config.key.incrementBid:
editBidPrice('increment');
break;
default:
break;
}
});
/**
* Simulate a click on Search button on transfer page
*/
const searchMarket = () => {
if (!isPageTitle("Transfermarkt-Suche")) {
log("Nicht in der transfermarket suche", 1);
return;
}
log("In der Transfermarkt-Suche");
const searchButton = document.querySelector('.btn-standard.call-to-action');
if (!searchButton) {
log("The search button was not found", true);
return;
}
if (config.noPlayerWarning) {
const playerName = document.querySelector('.ut-text-input-control').value;
if (playerName.length < 1) {
if (confirm("Kein Spieler wurde ausgewählt, trotzdem fortfahren?") !== true) {
return;
}
}
}
log("Search button found, attempting to search the market...");
tapElement(searchButton);
}
/**
* Automatically buy first player in search results
*/
const autoBuy = () => {
const numberOfTimes = config.autobuy.buyNowTimes;
const delay = 200; // ms
for (let i = 0; i < numberOfTimes; i++) {
setTimeout(() => {
buyNow();
}, delay * i);
}
}
/**
* Simulate a click on buy now button on selected items card
*/
const buyNow = () => {
if (!isPageTitle("SUCHERGEBNISSE")) {
log("Nicht in den Suchergebnissen", 1);
return;
}
const buyNowButton = document.querySelector('.btn-standard.buyButton.currency-coins');
if (!buyNowButton) {
log("No buy now button was found.", true);
return;
}
log("Attempting to buy the card...");
tapElement(buyNowButton);
}
/**
* Goes back.
*/
const goBack = () => {
if (!isPageTitle("SUCHERGEBNISSE")) {
return;
}
log('Attempting to go to the previous page...');
try {
const backButton = document.getElementsByClassName('ut-navigation-button-control')[0];
tapElement(backButton);
} catch (error) {
log('Unable to go back.', true);
return;
}
log('Successfully went back.');
}
/**
* Change selected item on search results
* @param {Event} event
*/
const move = (event) => {
try {
const isDown = event.keyCode === 40;
const itemList = document.querySelector('.ut-pinned-list > ul');
const items = Array.from(itemList.getElementsByClassName('listFUTItem'));
let currentIndex = items.findIndex((item) => {
return item.className.indexOf('selected') > -1;
});
if (isDown && currentIndex + 1 <= items.length) {
const div = items[++currentIndex].getElementsByClassName('has-tap-callback')[0];
tapElement(div);
} else if (!isDown && currentIndex - 1 >= 0) {
const div = items[--currentIndex].getElementsByClassName('has-tap-callback')[0];
tapElement(div);
}
} catch (error) {
log('Unable to change the currently selected item...', true);
return;
}
log('Successfully changed the currently selected item.');
}
/**
* Set a minimum bid price
* @param {integer} value
*/
const setBidPrice = (value) => {
const inputField = document.querySelector('.numericInput');
inputField.value = value;
}
/**
* Increment or decrement the minimum bid price
* @param {string} type
*/
const editBidPrice = (type) => {
const btn = document.querySelector('.btn-standard.' + type + '-value');
tapElement(btn);
}
/**
* Clear the minimum bid price.
*/
const clearBidPrice = () => {
const clearBtn = document.querySelector('.search-price-header > .flat.camel-case');
tapElement(clearBtn);
}
/**
* Check if current page title is equal to provided value
* @param {string} title
*/
const isPageTitle = (title) => {
const currentPageTitle = document.querySelector('h1.title').innerText;
return currentPageTitle.toUpperCase() === title.toUpperCase();
}
/**
* Add custom css styling
*/
const improveStyling = () => {
const style = document.createElement('style');
style.innerHTML = `
.ut-content-container { padding: 0; }
.ut-content-container .ut-content { border: 0; }
.ut-content-container .ut-content.ut-content--split-view-extend { max-height: 100%; }
.listFUTItem .entityContainer .name.untradeable { display: block;}
.listFUTItem .entityContainer .name.untradeable::before { position: relative; padding-right: 10px; }
.ut-club-search-results-view.ui-layout-left .listFUTItem .entityContainer,
.ut-unassigned-view.ui-layout-left .listFUTItem .entityContainer { width: 45%; }
@media (min-width: 1281px) {
.ut-content-container .ut-content { max-width: 100%; max-height: 100%; }
.ut-split-view .ut-content { max-width: 100%; max-height: 100%; }
}
`;
document.head.appendChild(style);
}
/**
* Logs a message to the console with app information.
*
* @param {string} message
* @param {boolean} isError
*/
const log = (message, isError) => {
if (!DEBUG_MODE) {
return;
}
let logFunction = console.info;
if (isError) {
logFunction = console.error;
}
logFunction(`${APP_NAME}: ${message}`)
}
/**
* Simulates a tap/click on an element.
*
* @param {HTMLElement} element
*/
const tapElement = (element) => {
sendTouchEvent(element, 'touchstart');
sendTouchEvent(element, 'touchend');
}
/**
* Dispatches a touch event on the element.
* https://stackoverflow.com/a/42447620
*
* @param {HTMLElement} element
* @param {string} eventType
*/
const sendTouchEvent = (element, eventType) => {
const touchObj = new Touch({
identifier: 'Keyboard shortcuts should be supported natively without an extension!',
target: element,
clientX: 0,
clientY: 0,
radiusX: 2.5,
radiusY: 2.5,
rotationAngle: 10,
force: 0.5
});
const touchEvent = new TouchEvent(eventType, {
cancelable: true,
bubbles: true,
touches: [touchObj],
targetTouches: [touchObj],
changedTouches: [touchObj],
shiftKey: true
});
element.dispatchEvent(touchEvent);
}
/**
* Instant buy now functionality
*/
if (config.instantBuyNow) {
const popupConfirm = utils.PopupManager.showConfirmation;
utils.PopupManager.showConfirmation = function (e, t, i, o) {
if (e.title === utils.PopupManager.Confirmations.CONFIRM_BUY_NOW.title) {
i();
return;
}
popupConfirm.call(this, e, t, i, o);
};
}
// Call the function to edit css...
improveStyling();
@hejamartin
Copy link

Watch out so you wont get banned ;)

@NenadGajic
Copy link
Author

Basically, the script is only simulating mouse clicks. :) If you use it with some care, there should not be a problem.

@Gourdboy
Copy link

Gourdboy commented Feb 3, 2021

good

@Patryksjj
Copy link

not working

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment