FUT
// ==UserScript== | |
// @name FUT Sniper | |
// @version 0.7 | |
// @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/de-de/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) { | |
utils.PopupManager.ShowConfirmation = (dialog, amount, proceed, s) => { | |
let cancel = s; | |
if (!utils.JS.isFunction(s)) { | |
cancel = function () { }; | |
} | |
if (dialog.title === utils.PopupManager.Confirmations.CONFIRM_BUY_NOW.title) { | |
proceed(); | |
return; | |
} | |
const n = new controllers.views.popups.Dialog( | |
dialog.message, dialog.title, | |
enums.UIDialogTypes.MESSAGE, amount, dialog.buttonLabels, | |
); | |
n.init(); | |
gPopupClickShield.setActivePopup(n); | |
n.onExit.observe(this, (e, t) => { | |
if (t !== enums.UIDialogOptions.CANCEL && t !== enums.UIDialogOptions.NO) { | |
if (proceed) { | |
proceed(); | |
} else if (cancel) { | |
cancel(); | |
} | |
} | |
}); | |
}; | |
} | |
// Call the function to edit css... | |
improveStyling(); |
This comment has been minimized.
This comment has been minimized.
Basically, the script is only simulating mouse clicks. :) If you use it with some care, there should not be a problem. |
This comment has been minimized.
This comment has been minimized.
good |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This comment has been minimized.
Watch out so you wont get banned ;)