Skip to content

Instantly share code, notes, and snippets.

@hearimm
Last active September 21, 2022 11:29
Show Gist options
  • Save hearimm/f1c21aee3beb2c2149d067b55a85d322 to your computer and use it in GitHub Desktop.
Save hearimm/f1c21aee3beb2c2149d067b55a85d322 to your computer and use it in GitHub Desktop.
fifa23 fut web app tampermonkey helper
// ==UserScript==
// @name Fifa 23 FUT Show Futbin player price
// @version 0.1
// @description Show the Futbin prices for players in the Search Results, Club Search and Trade Pile
// @license MIT
// @author Hyuk Choi
// @match https://www.ea.com/fifa/ultimate-team/web-app/
// @match https://www.ea.com/*/fifa/ultimate-team/web-app/*
// @namespace https://github.com/hearimm
// @grant GM_xmlhttpRequest
// @grant GM.xmlHttpRequest
// @grant GM_setClipboard
// @grant GM_addStyle
// @grant GM_getResourceText
// @connect www.futbin.com
// @require https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js
// @require https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js
// @require https://cdn.jsdelivr.net/npm/axios@^0.22.0/dist/axios.min.js
// @require https://cdn.jsdelivr.net/npm/axios-userscript-adapter@~0.1.7/dist/axiosGmxhrAdapter.min.js
// @require https://cdnjs.cloudflare.com/ajax/libs/jquery-toast-plugin/1.3.2/jquery.toast.min.js
// @resource jqToast_CSS https://cdnjs.cloudflare.com/ajax/libs/jquery-toast-plugin/1.3.2/jquery.toast.min.css
//
// Thanks to braceta for your work. i'm work just old codes. he's stuff https://github.com/braceta
// Thanks to Mardaneus86 for the inspiration
// Check he's stuff at https://github.com/Mardaneus86/futwebapp-tampermonkey/
// ==/UserScript==
var cssSrc = GM_getResourceText ("jqToast_CSS");
GM_addStyle(cssSrc);
console.log(axios.defaults.adapter);
axios.defaults.adapter = axiosGmxhrAdapter;
function toast(option) {
const defaultOption = {
text: 'hello world! <button>click me!</button>',
heading: 'Hello!', // Optional heading to be shown on the toast
loader: true,
loaderBg: '#9EC600',
showHideTransition: 'slide', // fade, slide or plain
allowToastClose: true, // Boolean value true or false
hideAfter: 10000, // false to make it sticky or number representing the miliseconds as time after which toast needs to be hidden
stack: 5, // false if there should be only one toast at a time or a number representing the maximum number of toasts to be shown at a time
position: 'top-right',
bgColor: '#444444',
textColor: '#eeeeee',
...option
}
$.toast(defaultOption);
}
var main = function() {
"use strict";
$("head").append(`<style id="addedCSS" type="text/css">
.listFUTItem .auction>.auction-state, .listFUTItem .auction>.auctionStartPrice, .listFUTItem .auction>.auctionValue {
flex: 1 1 30%;
overflow: hidden;
}
.ut-transfer-list-view .sectioned-item-list:nth-child(3) .listFUTItem .auction >div:not(.futbin) {
display: none;
}
.ut-club-search-results-view .listFUTItem .auction >div:not(.futbin) {
display: none;
}
.listFUTItem .auction .auction-state {
width: 25%;
float: right;
}
.listFUTItem .auction .auctionValue {
width: 24%;
float: left;
padding-right: 1%;
}
.listFUTItem .auction .futbin {
// background-color: lightgoldenrodyellow;
}
.listFUTItem .auction .futbin.oktobuy {
background-color: lightgreen;
}
.listFUTItem .auction .delta-green {
font-size: small;
color: green;
vertical-align: super;
}
.listFUTItem .auction .delta-red {
font-size: 14px;
color: red;
vertical-align: super;
}
.futbinupdate {
font-size: 14px;
clear: both;
display: block;
}
,currency-coins .value.futbin {
-webkit-filter: hue-rotate(165deg);
filter: hue-rotate(165deg);
}
.popup {
position: fixed;
width: 440px;
height: 40px;
right: 130px;
top: 4px;
z-index: 999999;
font-family: Helvetica;
background: lightgreen;
border-radius: 3px;
line-height: 40px;
padding: 0 10px;
}
.futbin-reload {
position: fixed;
width: 440px;
right: 300px;
top: 4px;
z-index: 999999;
}
.futbin-reload .btn {
border-radius: 4px;
--button-color: #212529;
--button-bg-color: #ffc107;
--button-hover-bg-color: #e0a800;
}
.popup .info {
font-weight:bold;
}
</style>`);
$("body").append(
'<div class="popup" style="display:none"><span "info">Best Page Deal:<span> <span class="name"></span> <span class="price"></span> <span class="gain"></span></div>'
);
$("body").append(
`<div class="futbin-reload">
<div class="button-container" style="padding:0">
<button id="fut-fetch" class="btn-standard mini call-to-action" type="button">Futbin Fetch</button>
<button id="fut-setData" class="btn-standard mini call-to-action" type="button">Set Data</button>
<div id="fut-autoFetch" class="ut-toggle-control-group-view">
<div class="ut-toggle-cell-view">
<span class="ut-toggle-cell-view--label">Auto Futbin Fetch</span>
<div class="ut-toggle-control">
<div class="ut-toggle-control--track"></div>
<div class="ut-toggle-control--grip"></div>
</div>
</div>
</div>
</div>
</div>`
);
var intervalTime = 1000;
var lastListRows = [];
var lastFutbinData = {};
var globalInterval;
var isAutoFetch = false;
$("#fut-fetch").click(function() {
fetchData();
});
$("#fut-setData").click(function() {
setData();
});
$("#fut-autoFetch").click(function() {
autoFetchToggle();
});
function run() {
globalInterval = setInterval(() => {
var listRows = getPagePlayerData();
if(_.isEmpty(listRows)) {
return;
}
if(isAutoFetch){
fetchData();
}
setData();
lastListRows = listRows
}, intervalTime);
}
run();
function hasFetchTarget(listRows) {
var playersIds = _.chain(listRows)
.filter(o => o.data.isPlayer)
.map(o => o.data.definitionId)
.uniq()
.filter(id => !_.has(lastFutbinData, id))
.value(); // 버튼 누를때 10개씩 가져오도록
return playersIds.length > 0
}
function fetchData() {
var listRows = getPagePlayerData();
if (_.isEmpty(listRows)) {
return;
}
if(!hasFetchTarget(listRows)){
return;
}
fetchFutbinPlayerPrices(listRows);
}
function getPagePlayerData() {
var playerElements = document.querySelectorAll(".listFUTItem .player");
if (playerElements.length <= 0) {
return [];
}
// Club search or Player search
if (isClubSearchController() || isTransferMarketSearchController()) {
return getCurrentController()._listController._view._list.listRows;
}
// Transfer list
if (isTransferListController() || isWatchListController()) {
return getCurrentController()._listController._view.sections.flatMap(a => a.listRows);
}
}
function isTransferMarketSearchController() {
return getCurrentControllerClassName() === "UTMarketSearchResultsSplitViewController";
}
function isClubSearchController() {
return getCurrentControllerClassName() === "ClubSearchResultsSplitViewController";
}
function isTransferListController() {
return getCurrentControllerClassName() === "UTTransferListSplitViewController";
}
function isWatchListController() {
return getCurrentControllerClassName() === "UTWatchListSplitViewController";
}
function getCurrentController() {
return getAppMain()
.getRootViewController()
.getPresentedViewController()
.getCurrentViewController()
.getCurrentController();
}
function getCurrentControllerClassName() {
return getCurrentController().className;
}
function fetchFutbinPlayerPrices(listRows) {
console.log('fetch FutbinPlayerPrices listRows',listRows)
var playersIds = _.chain(listRows)
.filter(o => o.data.isPlayer)
.map(o => o.data.definitionId)
.uniq()
.filter(id => !_.has(lastFutbinData, id))
.slice(0,10)
.value(); // 버튼 누를때 10개씩 가져오도록
console.log('playersIds',playersIds)
var futbinUrl = "https://www.futbin.com/23/playerPrices?player=";
const axiosList = _.map(playersIds, playerId => axios.get(futbinUrl+playerId))
Promise.all(axiosList).then(function(values) {
const dataList = _.map(values, o => o.data)
const futbinData = Object.assign(...dataList)
lastFutbinData = Object.assign(lastFutbinData, futbinData);
console.log(lastFutbinData);
showFutbinPriceInPage(listRows, lastFutbinData);
// showBestDealMessage(listRows, lastFutbinData);
});
}
function showFutbinPriceInPage(listRows, futbinData) {
listRows.map((item, idx) => {
if(!isAuctionShowTarget(futbinData, item, idx)){
return;
}
setListFutItemAddClass(futbinData, item, idx);
setListFutItemClickEvent(futbinData, item, idx);
setListFutItemAuctionDom(futbinData, item, idx);
});
}
function isAuctionShowTarget(futbinData, item, idx) {
if (!futbinData) {
return false;
}
if (!item.data.isPlayer()) {
return false;
}
var playerId = item.data.definitionId;
if (!futbinData[playerId]) {
//console.error('not found playerId from futbinData:', futbinData, playerId);
return false; // futbin data might not be available for this player
}
var targetDomElements = jQuery(".listFUTItem").get();
var target = jQuery(targetDomElements[idx]);
if (target.find(".futbin").length > 0) {
return false; // futbin price already added to the row
}
return true;
}
function setListFutItemAddClass(futbinData, item, idx) {
var targetDomElements = jQuery(".listFUTItem").get();
var target = jQuery(targetDomElements[idx]);
if(!target.hasClass('has-auction-data')){
target.addClass('has-auction-data');
}
}
function setListFutItemClickEvent(futbinData, item, idx) {
var targetDomElements = jQuery(".listFUTItem").get();
var target = jQuery(targetDomElements[idx]);
var futbinPriceRaw = getFutbinPriceRaw(futbinData, item);
target.click(
function() {
toast({heading: "Copied Futbin Price ", text: futbinPriceRaw });
GM_setClipboard(futbinPriceRaw, { type: 'text', mimetype: 'text/plain'})
}
);
}
function setListFutItemAuctionDom(futbinData, item, idx) {
var targetDomElements = jQuery(".listFUTItem").get();
var target = jQuery(targetDomElements[idx]);
var targetAuctionDom = target.find(".auction");
targetAuctionDom.show();
targetAuctionDom.append(getAppendAuctionHtmlStr(futbinData, item));
}
function getAppendAuctionHtmlStr(futbinData, item) {
var okToBuyClass = getOkToBuyClass(futbinData, item);
var futBinDataUpated = getFutBinDataUpated(futbinData, item);
var futbinPriceRawText = getFutbinPriceRawText(futbinData, item);
return `
<div class="auctionValue futbin ${okToBuyClass}">
<span class="label">Futbin Updated:</span>
<span class="value">${futBinDataUpated}</span>
</div>
<div class="auctionValue futbin ${okToBuyClass}">
<span class="label">Futbin Price:</span>
<span class="currency-coins value">${futbinPriceRawText}</span>
</div>
`
}
function getOkToBuyClass(futbinData, item) {
var futbinPriceRaw = getFutbinPriceRaw(futbinData, item);
if (!isTransferMarketSearchController()) {
return;
}
// Stuff for transfermarket
var futbinPrice = parseInt(futbinPriceRaw.replace(/,/g, ""));
var isBuyNowLowerThanFutbinPrice = item.data.getAuctionData().buyNowPrice < futbinPrice;
return isBuyNowLowerThanFutbinPrice ? "oktobuy" : "";
}
function getFutbinPriceRaw(futbinData, item) {
var playerId = item.data.definitionId;
var platform = getPersonaPlatform();
return futbinData[playerId].prices[platform].LCPrice;
}
function getPersonaPlatform() {
var platform = "";
if (services.User.getUser().getSelectedPersona().isPlaystation) platform = "ps";
if (services.User.getUser().getSelectedPersona().isPC) platform = "pc";
if (services.User.getUser().getSelectedPersona().isXbox) platform = "xbox";
return platform;
}
function getFutBinDataUpated(futbinData, item){
var playerId = item.data.definitionId;
var platform = getPersonaPlatform();
return futbinData[playerId].prices[platform].updated
}
function getFutbinPriceRawText(futbinData, item) {
var futbinPriceRaw = getFutbinPriceRaw(futbinData, item);
if (!isTransferMarketSearchController()) {
return futbinPriceRaw;
}
// Stuff for transfermarket
var futbinPrice = parseInt(futbinPriceRaw.replace(/,/g, ""));
var delta = item.data.getAuctionData().buyNowPrice - futbinPrice;
var deltaClass = delta < 0 ? "delta-green" : "delta-red";
var deltaString = delta > 0 ? "+" + delta : delta;
return futbinPriceRaw + '<span class="' + deltaClass + '">(' + deltaString + ")</span>";
}
// function showBestDealMessage(listRows, futbinData) {
// if (!isTransferMarketSearchController()) {
// return;
// }
// var bestDeal = {
// item: "",
// price: 0,
// futbinPrice: 0,
// gain: 0
// };
// listRows.map((item, idx) => {
// if(!isAuctionShowTarget(futbinData, item, idx)){
// return;
// }
// bestDeal = getBestDeal(futbinData, item, bestDeal);
// });
// toastBestDeal(bestDeal);
// }
// function getBestDeal(futbinData, item, bestDeal) {
// // Stuff for transfermarket
// var futbinPriceRaw = getFutbinPriceRaw(futbinData, item);
// var futbinPrice = parseInt(futbinPriceRaw.replace(/,/g, ""));
// var delta = item.data.getAuctionData().buyNowPrice - futbinPrice;
// if (delta < bestDeal.gain) {
// bestDeal.gain = delta;
// bestDeal.name = item.data.getStaticData().name;
// bestDeal.futbinPrice = futbinPrice;
// bestDeal.price = item.data.getAuctionData().buyNowPrice;
// }
// return {
// ...bestDeal,
// gain: delta,
// name: item.data.getStaticData().name,
// futbinPrice: futbinPrice,
// price: item.data.getAuctionData().buyNowPrice
// }
// }
// function toastBestDeal(bestDeal) {
// if (bestDeal.gain === 0) {
// toast({heading: "Best Deal", text: getBestDealText('Nothing found', 'None', 'None')});
// } else {
// toast({heading: "Best Deal", text: getBestDealText(bestDeal.name, bestDeal.price, `${bestDeal.gain} (coins cheaper)`)});
// }
// }
function getBestDealText(name, price, gain) {
return `
Best Page Deal: ${name}
<br>Best Page Price: ${price}</br>
<br>Best Page Gain: ${gain} (coins cheaper)</br>
`;
}
function setData() {
var listRows = getPagePlayerData();
if (_.isEmpty(listRows)) {
return;
}
showFutbinPriceInPage(listRows, lastFutbinData);
// showBestDealMessage(listRows, lastFutbinData);
}
function autoFetchToggle() {
isAutoFetch = !isAutoFetch;
if(isAutoFetch){
$('#fut-autoFetch .ut-toggle-control').addClass('toggled');
}else {
$('#fut-autoFetch .ut-toggle-control').removeClass('toggled');
}
}
};
$(document).ready(function () {
main()
});
(async function() {
function isIncludeText(url, txt){
return url.includes(txt)
}
// function getCopyData(json){ //복사할 데이터
// const maxBid = getMaxBid(json);
// const minBuyNowPrice = getMinBuyNowPrice(json);
// if(maxBid == 0) {
// return minBuyNowPrice -50; // 즉구가일땐 -50원
// }
// const resultArr = [maxBid + 100 , minBuyNowPrice - 50]; // 비딩가에선 +100원, 즉구가일땐 -50원
// const sorted = resultArr.sort((a,b) => { return a - b })
// return sorted[0];
// }
// function getMaxBid(json) { // 30초 미만 비딩중 제일 비싼가격
// const mapped = json.auctionInfo.map(x => {
// if(x.currentBid > 0 && x.expires < 30){
// return x.currentBid;
// }
// });
// const sorted = mapped.sort((a,b) => { return b - a })
// const maxBid = sorted[0];
// if(maxBid == null){
// return 0;
// }
// console.log('maxBid',maxBid);
// return maxBid;
// }
function getMinBuyNowPrice(json) { // 즉구가 중 가잔 싼 가격
if(_.isEmpty(json.auctionInfo)) { return 0 }
const mapped = json.auctionInfo.map(x => {
return x.buyNowPrice;
});
const sorted = mapped.sort((a,b) => { return a - b })
const minBuyNowPrice = sorted[0];
console.log('minBuyNowPrice',minBuyNowPrice);
return minBuyNowPrice
}
function auctionSellReq(id, startingBid, buyNowPrice) {
console.log('bidBuyNowReq tradeId', tradeId);
console.log('bidBuyNowReq buyNowPrice', buyNowPrice);
var xhr = new XMLHttpRequest();
xhr.open("PUT", getAuctionSellUrl(), true);
xhr.setRequestHeader('Content-type','application/json');
xhr.setRequestHeader('X-UT-SID',sid);
xhr.onload = function () {
if (isSuccessOnLoad(xhr)) {
toastSuccess({heading: "auction success 200! ", text: xhr.responseText });
} else {
toast({heading: "auction fail error ", text: "status : "+xhr.status + " : " + xhr.responseText , bgColor: "#d9534f" });
console.error(xhr.responseText);
}
}
xhr.send(JSON.stringify({ "bid": buyNowPrice }));
}
function getAuctionSellUrl() {
return 'https://utas.external.s2.fut.ea.com/ut/game/fifa23/auctionhouse'
}
let oldXHROpen = window.XMLHttpRequest.prototype.open;
window.XMLHttpRequest.prototype.open = function(method, url, async, user, password) {
// do something with the method, url and etc.
this.addEventListener('load', function() {
// do something with the response text
if( isIncludeText(this.responseURL,'transfermarket')){
var data = this.responseText;
var json = JSON.parse(data);
const copyData = getMinBuyNowPrice(json);
if(copyData <= 0) { return }
toast({heading: "Copied Min BuyNow Price ", text: copyData });
GM_setClipboard(copyData, { type: 'text', mimetype: 'text/plain'})
}
});
return oldXHROpen.apply(this, arguments);
}
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment