├── .gitignore ├── .github ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── custom.yml │ ├── feature_request.yml │ └── bug_report.yml ├── FUNDING.yml └── workflows │ ├── webpack.yml │ ├── close_stale_issues.yml │ └── codeql-analysis.yml ├── .vs ├── ProjectSettings.json ├── slnx.sqlite ├── Fifa21-AutoBuyer │ └── v16 │ │ └── .suo └── VSWorkspaceState.json ├── storeImg ├── appstore-badge.png └── google-play-badge.png ├── app ├── services │ ├── datasource │ │ ├── index.js │ │ ├── futwiz.js │ │ └── futbin.js │ ├── listeners.js │ ├── externalRequest.js │ ├── analytics.js │ └── repository.js ├── utils │ ├── popupUtil.js │ ├── dbUtil.js │ ├── networkUtil.js │ ├── userUtil.js │ ├── playerUtil.js │ ├── timeOutUtil.js │ ├── filterUtil.js │ ├── uiUtils │ │ ├── generateButton.js │ │ ├── generateToggleInput.js │ │ └── generateTextInput.js │ ├── futItemUtil.js │ ├── priceUtils.js │ ├── processorUtil.js │ ├── logUtil.js │ ├── statsUtil.js │ ├── phoneDbUtil.js │ ├── webDbUtil.js │ ├── futbinUtil.js │ ├── transferlistUtil.js │ ├── sellUtil.js │ ├── filterSyncUtil.js │ ├── autoActionsUtil.js │ ├── userExternalUtil.js │ ├── commonUtil.js │ ├── purchaseUtil.js │ ├── notificationUtil.js │ ├── watchlistUtil.js │ └── searchUtil.js ├── views │ ├── layouts │ │ ├── ButtonView.js │ │ ├── LogView.js │ │ ├── Settings │ │ │ ├── SettingsVarId.js │ │ │ ├── CaptchaSettingsView.js │ │ │ ├── CommonSettingsView.js │ │ │ ├── SafeSettingsView.js │ │ │ ├── BuySettingsView.js │ │ │ ├── SellSettingsView.js │ │ │ ├── NotificationSettingsView.js │ │ │ ├── SearchSettingsView.js │ │ │ └── FilterSettingsView.js │ │ ├── HeaderView.js │ │ └── MenuItemView.js │ └── AutoBuyerViewController.js ├── function-overrides │ ├── index.js │ ├── currencyTap-override.js │ ├── topnav-override.js │ ├── sidebarnav-override.js │ ├── network-override.js │ ├── NetworkIntercepts │ │ └── index.js │ └── css-override.js ├── index.js ├── handlers │ ├── errorHandler.js │ ├── statsProcessor.js │ ├── autobuyerProcessor.js │ └── captchaSolver.js ├── app.constants.js └── elementIds.constants.js ├── webpack.config.js ├── package.json ├── tampermonkey-header.js └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | node_modules 3 | dist -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | -------------------------------------------------------------------------------- /.vs/ProjectSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "CurrentProjectSetting": null 3 | } -------------------------------------------------------------------------------- /.vs/slnx.sqlite: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ckalgos/FUT-Trader/HEAD/.vs/slnx.sqlite -------------------------------------------------------------------------------- /storeImg/appstore-badge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ckalgos/FUT-Trader/HEAD/storeImg/appstore-badge.png -------------------------------------------------------------------------------- /.vs/Fifa21-AutoBuyer/v16/.suo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ckalgos/FUT-Trader/HEAD/.vs/Fifa21-AutoBuyer/v16/.suo -------------------------------------------------------------------------------- /storeImg/google-play-badge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ckalgos/FUT-Trader/HEAD/storeImg/google-play-badge.png -------------------------------------------------------------------------------- /.vs/VSWorkspaceState.json: -------------------------------------------------------------------------------- 1 | { 2 | "ExpandedNodes": [ 3 | "" 4 | ], 5 | "SelectedNode": "\\autobuyer.js", 6 | "PreviewInSolutionExplorer": false 7 | } -------------------------------------------------------------------------------- /app/services/datasource/index.js: -------------------------------------------------------------------------------- 1 | import { getDataSource } from "../repository"; 2 | import futwiz from "./futwiz"; 3 | import futbin from "./futbin"; 4 | 5 | export const fetchPrices = async (items) => { 6 | const dataSource = getDataSource(); 7 | 8 | if (dataSource === "FUTWIZ") { 9 | return futwiz.fetchPrices(items); 10 | } else if (dataSource === "FUTBIN") { 11 | return futbin.fetchPrices(items); 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /app/utils/popupUtil.js: -------------------------------------------------------------------------------- 1 | export const showPopUp = (options, title, message, selectCallBack) => { 2 | const messagePopUp = new EADialogViewController({ 3 | dialogOptions: options, 4 | message, 5 | title, 6 | }); 7 | messagePopUp.init(); 8 | messagePopUp.onExit.observe(this, function (e, t) { 9 | e.unobserve(this), selectCallBack.call(this, t); 10 | }); 11 | gPopupClickShield.setActivePopup(messagePopUp); 12 | }; 13 | -------------------------------------------------------------------------------- /app/views/layouts/ButtonView.js: -------------------------------------------------------------------------------- 1 | export const createButton = function (text, callBack, customClass) { 2 | const stdButton = new UTStandardButtonControl(); 3 | stdButton.init(); 4 | stdButton.addTarget(stdButton, callBack, EventType.TAP); 5 | stdButton.setText(text); 6 | 7 | if (customClass) { 8 | const classes = customClass.split(" "); 9 | for (let cl of classes) stdButton.getRootElement().classList.add(cl); 10 | } 11 | 12 | return stdButton; 13 | }; 14 | -------------------------------------------------------------------------------- /app/utils/dbUtil.js: -------------------------------------------------------------------------------- 1 | import phoneDBUtil from "./phoneDBUtil"; 2 | 3 | export const initDatabase = () => phoneDBUtil.initDatabase(); 4 | 5 | export const getUserFilters = (storeName = "Filters") => 6 | phoneDBUtil.getUserFilters(storeName); 7 | 8 | export const insertFilters = (filterName, jsonData, storeName = "Filters") => 9 | phoneDBUtil.insertFilters(filterName, jsonData, storeName); 10 | 11 | export const deleteFilters = (filterName) => 12 | phoneDBUtil.deleteFilters(filterName); 13 | -------------------------------------------------------------------------------- /app/utils/networkUtil.js: -------------------------------------------------------------------------------- 1 | import { sendExternalRequest } from "../services/externalRequest"; 2 | 3 | export const sendRequest = (url, method, identifier) => { 4 | return new Promise((resolve, reject) => { 5 | sendExternalRequest({ 6 | method, 7 | identifier, 8 | url, 9 | onload: (res) => { 10 | if (res.status !== 200) { 11 | return reject(); 12 | } 13 | return resolve(res.response); 14 | }, 15 | }); 16 | }); 17 | }; 18 | -------------------------------------------------------------------------------- /app/function-overrides/index.js: -------------------------------------------------------------------------------- 1 | import { initDatabase } from "../utils/dbUtil"; 2 | import { xmlRequestOverride } from "./NetworkIntercepts"; 3 | import { sideBarNavOverride } from "./sidebarnav-override"; 4 | import { topNavOverride } from "./topnav-override"; 5 | 6 | import "./network-override"; 7 | import { currencyTapOverride } from "./currencyTap-override"; 8 | 9 | export const initOverrides = () => { 10 | initDatabase(); 11 | sideBarNavOverride(); 12 | isPhone() && topNavOverride(); 13 | xmlRequestOverride(); 14 | currencyTapOverride(); 15 | }; 16 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const headers = require("./tampermonkey-header"); 2 | const WebpackUserscript = require("webpack-userscript"); 3 | const TerserPlugin = require("terser-webpack-plugin"); 4 | 5 | module.exports = { 6 | entry: "./app/index.js", 7 | output: { 8 | filename: "./fut-auto-buyer.user.js", 9 | }, 10 | devServer: { 11 | contentBase: "./dist/", 12 | }, 13 | plugins: [ 14 | new WebpackUserscript({ 15 | ...headers, 16 | }), 17 | ], 18 | optimization: { 19 | minimize: true, 20 | minimizer: [new TerserPlugin()], 21 | }, 22 | }; 23 | -------------------------------------------------------------------------------- /app/services/listeners.js: -------------------------------------------------------------------------------- 1 | import { getValue, setValue } from "./repository"; 2 | 3 | export const initListeners = () => { 4 | window.addEventListener( 5 | "message", 6 | (payload) => { 7 | const data = JSON.parse(payload.data); 8 | switch (data.type) { 9 | case "dataFromExternalAB": { 10 | const { res, identifier } = data.response; 11 | const callBack = getValue(identifier); 12 | callBack && callBack(res); 13 | return setValue(identifier, null); 14 | } 15 | } 16 | }, 17 | true 18 | ); 19 | }; 20 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with a single Open Collective username 4 | patreon: ckalgos 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: [""] 13 | -------------------------------------------------------------------------------- /app/utils/userUtil.js: -------------------------------------------------------------------------------- 1 | import { getValue, setValue } from "../services/repository"; 2 | 3 | export const getUserPlatform = () => { 4 | let platform = getValue("userPlatform"); 5 | if (platform) return platform; 6 | 7 | if (services.User.getUser().getSelectedPersona().isPC) { 8 | setValue("userPlatform", "pc"); 9 | return "pc"; 10 | } 11 | 12 | setValue("userPlatform", "ps"); 13 | return "ps"; 14 | }; 15 | 16 | export const updateUserCredits = () => { 17 | return new Promise((resolve) => { 18 | services.User.requestCurrencies().observe(this, function (sender, data) { 19 | resolve(); 20 | }); 21 | }); 22 | }; 23 | -------------------------------------------------------------------------------- /app/utils/playerUtil.js: -------------------------------------------------------------------------------- 1 | export const sortPlayers = (playerList, sortBy, sortOrder) => { 2 | let sortFunc = (a) => a._auction.buyNowPrice; 3 | if (sortBy === "bid") { 4 | sortFunc = (a) => a._auction.currentBid || a._auction.startingBid; 5 | } else if (sortBy === "rating") { 6 | sortFunc = (a) => parseInt(a.rating); 7 | } else if (sortBy === "expires") { 8 | sortFunc = (a) => parseInt(a._auction.expires); 9 | } 10 | playerList.sort((a, b) => { 11 | const sortAValue = sortFunc(a); 12 | const sortBValue = sortFunc(b); 13 | return !sortOrder ? sortBValue - sortAValue : sortAValue - sortBValue; 14 | }); 15 | return playerList; 16 | }; 17 | -------------------------------------------------------------------------------- /app/function-overrides/currencyTap-override.js: -------------------------------------------------------------------------------- 1 | import { resetProfit } from "../utils/statsUtil"; 2 | import { updateUserCredits } from "../utils/userUtil"; 3 | 4 | export const currencyTapOverride = () => { 5 | const currentTap = UTCurrencyNavigationBarView.prototype._tapDetected; 6 | UTCurrencyNavigationBarView.prototype._tapDetected = function (e) { 7 | const res = currentTap.call(this, e); 8 | const isCoins = e.target.classList.contains("coins"); 9 | if (isCoins) { 10 | updateUserCredits(); 11 | return res; 12 | } 13 | const isProfit = e.target.classList.contains("profit"); 14 | isProfit && resetProfit(); 15 | return res; 16 | }; 17 | }; 18 | -------------------------------------------------------------------------------- /.github/workflows/webpack.yml: -------------------------------------------------------------------------------- 1 | name: Generate userscript 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | 13 | strategy: 14 | matrix: 15 | node-version: [14.x] 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | 20 | - name: Use Node.js ${{ matrix.node-version }} 21 | uses: actions/setup-node@v1 22 | with: 23 | node-version: ${{ matrix.node-version }} 24 | 25 | - name: Build 26 | run: | 27 | npm install 28 | npm run build:prod 29 | 30 | - name: Upload a Build Artifact 31 | uses: actions/upload-artifact@v2.3.1 32 | with: 33 | name: fut-auto-buyer-build 34 | path: ./dist 35 | -------------------------------------------------------------------------------- /app/services/externalRequest.js: -------------------------------------------------------------------------------- 1 | import { isMarketAlertApp } from "../app.constants"; 2 | import { idSession } from "../elementIds.constants"; 3 | import { setValue } from "../services/repository"; 4 | 5 | export const sendExternalRequest = async (options) => { 6 | if (isMarketAlertApp) { 7 | sendPhoneRequest(options); 8 | } else { 9 | sendWebRequest(options); 10 | } 11 | }; 12 | 13 | const sendPhoneRequest = (options) => { 14 | setValue(options.identifier, options.onload); 15 | delete options["onload"]; 16 | window.ReactNativeWebView.postMessage( 17 | JSON.stringify({ type: "fetchFromExternalAB", payload: { options } }) 18 | ); 19 | }; 20 | 21 | const sendWebRequest = (options) => { 22 | GM_xmlhttpRequest({ 23 | method: options.method, 24 | url: options.url, 25 | onload: options.onload, 26 | headers: { "User-Agent": idSession }, 27 | }); 28 | }; 29 | -------------------------------------------------------------------------------- /app/utils/timeOutUtil.js: -------------------------------------------------------------------------------- 1 | import { setValue } from "../services/repository"; 2 | 3 | export const setRandomInterval = (intervalFunction, start, end) => { 4 | let timeout; 5 | let isCleared = false; 6 | 7 | const runInterval = () => { 8 | if (isCleared) return; 9 | const searchInterval = { 10 | start: Date.now(), 11 | }; 12 | const timeoutFunction = () => { 13 | intervalFunction(); 14 | runInterval(); 15 | }; 16 | 17 | const delay = 18 | parseFloat((Math.random() * (end - start) + start).toFixed(1)) * 1000; 19 | searchInterval.end = searchInterval.start + delay; 20 | setValue("searchInterval", searchInterval); 21 | timeout = setTimeout(timeoutFunction, delay); 22 | }; 23 | 24 | runInterval(); 25 | 26 | return { 27 | clear() { 28 | isCleared = true; 29 | clearTimeout(timeout); 30 | }, 31 | }; 32 | }; 33 | -------------------------------------------------------------------------------- /app/utils/filterUtil.js: -------------------------------------------------------------------------------- 1 | import { idSelectedFilter } from "../elementIds.constants"; 2 | import { setValue } from "../services/repository"; 3 | 4 | export const checkAndAppendOption = function (dropdownSelector, optionName) { 5 | let exist = false; 6 | $(`${dropdownSelector} option`).each(function () { 7 | if (this.value === optionName) { 8 | exist = true; 9 | return false; 10 | } 11 | }); 12 | 13 | if (!exist) { 14 | $(dropdownSelector).append( 15 | $("").attr("value", optionName).text(optionName) 16 | ); 17 | } 18 | return exist; 19 | }; 20 | 21 | export const updateMultiFilterSettings = function () { 22 | const selectedFilters = $(`#${idSelectedFilter}`).val() || []; 23 | if (selectedFilters.length) { 24 | setValue("selectedFilters", selectedFilters); 25 | } else { 26 | setValue("selectedFilters", []); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /.github/workflows/close_stale_issues.yml: -------------------------------------------------------------------------------- 1 | name: Close inactive issues 2 | on: 3 | schedule: 4 | - cron: "30 1 * * *" 5 | workflow_dispatch: 6 | 7 | jobs: 8 | close-issues: 9 | runs-on: ubuntu-latest 10 | permissions: 11 | issues: write 12 | pull-requests: write 13 | steps: 14 | - uses: actions/stale@v4 15 | with: 16 | days-before-issue-stale: 30 17 | days-before-issue-close: 14 18 | stale-issue-label: "stale" 19 | stale-issue-message: "This issue is stale because it has been open for 30 days with no activity." 20 | close-issue-message: "This issue was closed because it has been inactive for 14 days since being marked as stale." 21 | days-before-pr-stale: -1 22 | days-before-pr-close: -1 23 | exempt-issue-labels: "enhancement" 24 | repo-token: ${{ secrets.GITHUB_TOKEN }} 25 | -------------------------------------------------------------------------------- /app/services/analytics.js: -------------------------------------------------------------------------------- 1 | import { getValue, setValue } from "./repository"; 2 | const sendRequest = (url, payload, token = null) => { 3 | return fetch(url, { 4 | headers: { 5 | Accept: "'application/json'", 6 | "Content-Type": "'application/json'", 7 | Authorization: token ? "Bearer " + token : null, 8 | }, 9 | method: "POST", 10 | body: JSON.stringify(payload), 11 | }); 12 | }; 13 | 14 | const generateToken = () => { 15 | const email = getValue("useremail"); 16 | const { id: userId } = services.User.getUser(); 17 | return sendRequest( 18 | atob( 19 | "aHR0cHM6Ly8zcHFtaHc3NmE0LmV4ZWN1dGUtYXBpLmV1LXdlc3QtMS5hbWF6b25hd3MuY29tL2Rldi90b2tlbg==" 20 | ), 21 | { email, userId } 22 | ); 23 | }; 24 | 25 | export const trackMarketPrices = async (playerPrices) => { 26 | return sendRequest(atob("aHR0cHM6Ly9hcGkuZnV0aGVscGVycy5jb20vYXVjdGlvbg=="), { 27 | playerPrices, 28 | }); 29 | }; 30 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fut-auto-buyer", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "build:dev": "webpack --mode development", 8 | "build:prod": "webpack --mode production", 9 | "serve": "webpack serve --mode development", 10 | "build:mobile": "npx babel dist/fut-auto-buyer.user.js --out-file dist/fut-auto-buyer.mobile.js --presets babel-preset-es2015" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/ckalgos/fut-auto-buyer.git" 15 | }, 16 | "author": "Ck Algos", 17 | "license": "ISC", 18 | "dependencies": { 19 | "webpack": "^5.47.1" 20 | }, 21 | "devDependencies": { 22 | "babel-cli": "^6.26.0", 23 | "babel-preset-es2015": "^6.24.1", 24 | "terser-webpack-plugin": "^5.1.4", 25 | "webpack-cli": "^4.10.0", 26 | "webpack-dev-server": "^3.11.2", 27 | "webpack-userscript": "^2.5.8" 28 | } 29 | } -------------------------------------------------------------------------------- /app/utils/uiUtils/generateButton.js: -------------------------------------------------------------------------------- 1 | let eventMappers = new Set(); 2 | 3 | export const generateButton = (id, label, callback, additionalClasses) => { 4 | if (!eventMappers.has(id)) { 5 | initializeListensers(id, callback); 6 | eventMappers.add(id); 7 | } 8 | return ``; 9 | }; 10 | 11 | const initializeListensers = (id, callback) => { 12 | $(document).on( 13 | { 14 | mouseenter: function () { 15 | $(this).addClass("hover"); 16 | }, 17 | mouseleave: function () { 18 | $(this).removeClass("hover"); 19 | }, 20 | click: function () { 21 | callback(); 22 | }, 23 | touchenter: function () { 24 | $(this).addClass("hover"); 25 | }, 26 | touchleave: function () { 27 | $(this).removeClass("hover"); 28 | }, 29 | touchend: function () { 30 | callback(); 31 | }, 32 | }, 33 | `#${id}` 34 | ); 35 | }; 36 | -------------------------------------------------------------------------------- /app/utils/futItemUtil.js: -------------------------------------------------------------------------------- 1 | export const getCardName = (card) => { 2 | const translationService = services.Localization; 3 | if (!translationService) { 4 | return ""; 5 | } 6 | if (card.isManagerContract()) { 7 | return translationService.localize("card.title.managercontracts"); 8 | } else if (card.isPlayerContract()) { 9 | return translationService.localize("card.title.playercontracts"); 10 | } else if (card.isStyleModifier()) { 11 | return UTLocalizationUtil.playStyleIdToName( 12 | card.subtype, 13 | translationService 14 | ); 15 | } else if (card.isPlayerPositionModifier()) { 16 | return translationService 17 | .localize( 18 | "card.desc.training.pos." + 19 | card._staticData.trainPosFrom + 20 | "_" + 21 | card._staticData.trainPosTo 22 | ) 23 | .replace(" >> ", "->"); 24 | } 25 | return card._staticData.name; 26 | }; 27 | 28 | export const checkRating = ( 29 | cardRating, 30 | permittedRatingMin, 31 | permittedRatingMax 32 | ) => cardRating >= permittedRatingMin && cardRating <= permittedRatingMax; 33 | -------------------------------------------------------------------------------- /app/utils/priceUtils.js: -------------------------------------------------------------------------------- 1 | export const roundOffPrice = (price) => { 2 | let range = JSUtils.find(UTCurrencyInputControl.PRICE_TIERS, function (e) { 3 | return price >= e.min; 4 | }); 5 | var nearestPrice = Math.round(price / range.inc) * range.inc; 6 | return Math.max(Math.min(nearestPrice, 14999000), 0); 7 | }; 8 | 9 | export const getSellBidPrice = (bin) => { 10 | if (bin <= 1000) { 11 | return bin - 50; 12 | } 13 | 14 | if (bin > 1000 && bin <= 10000) { 15 | return bin - 100; 16 | } 17 | 18 | if (bin > 10000 && bin <= 50000) { 19 | return bin - 250; 20 | } 21 | 22 | if (bin > 50000 && bin <= 100000) { 23 | return bin - 500; 24 | } 25 | 26 | return bin - 1000; 27 | }; 28 | 29 | export const getBuyBidPrice = (bin) => { 30 | if (bin < 1000) { 31 | return bin + 50; 32 | } 33 | 34 | if (bin >= 1000 && bin < 10000) { 35 | return bin + 100; 36 | } 37 | 38 | if (bin >= 10000 && bin < 50000) { 39 | return bin + 250; 40 | } 41 | 42 | if (bin >= 50000 && bin < 100000) { 43 | return bin + 500; 44 | } 45 | 46 | return bin + 1000; 47 | }; 48 | -------------------------------------------------------------------------------- /app/services/repository.js: -------------------------------------------------------------------------------- 1 | const lookUp = new Map(); 2 | 3 | export const setValue = (key, value) => { 4 | lookUp.set(key, value); 5 | }; 6 | 7 | export const getValue = (key) => { 8 | const value = lookUp.get(key); 9 | if (value && value.expiryTimeStamp && value.expiryTimeStamp < Date.now()) { 10 | lookUp.delete(key); 11 | return null; 12 | } 13 | return value; 14 | }; 15 | 16 | export const getBuyerSettings = (ignoreCommonSetting = false) => { 17 | const buyerSetting = getValue("BuyerSettings") || {}; 18 | if (ignoreCommonSetting) { 19 | return buyerSetting; 20 | } 21 | const commonSettings = getValue("CommonSettings") || {}; 22 | return Object.assign({}, buyerSetting, commonSettings); 23 | }; 24 | 25 | export const increAndGetStoreValue = (key) => { 26 | let storeValue = getValue(key) || 0; 27 | storeValue++; 28 | setValue(key, storeValue); 29 | return storeValue; 30 | }; 31 | 32 | export const getDataSource = () => { 33 | const commonSettings = getValue("CommonSettings") || {}; 34 | return (commonSettings["idAbUseFutWiz"] ? "futwiz" : "futbin").toUpperCase(); 35 | }; 36 | -------------------------------------------------------------------------------- /app/function-overrides/topnav-override.js: -------------------------------------------------------------------------------- 1 | export const topNavOverride = () => { 2 | const layoutSubViews = UTNavigationBarView.prototype.layoutSubviews; 3 | UTNavigationBarView.prototype.layoutSubviews = function (...args) { 4 | const result = layoutSubViews.call(this, ...args); 5 | if (this.primaryButton && this.__clubInfo) { 6 | this._menuBtn && this._menuBtn.removeFromSuperview(); 7 | this._menuBtn = generateNavButton.call(this); 8 | const settingBtn = $(this.primaryButton.getRootElement()); 9 | const menuBtn = $(this._menuBtn.getRootElement()); 10 | $(".top-nav").remove(); 11 | settingBtn.wrap(`
`); 12 | menuBtn.insertBefore(settingBtn); 13 | } 14 | return result; 15 | }; 16 | 17 | function generateNavButton() { 18 | const _menuBtn = new UTNavigationButtonControl(); 19 | _menuBtn.init(); 20 | _menuBtn.addClass("menu-btn"); 21 | _menuBtn.setInteractionState(!0); 22 | _menuBtn.addTarget( 23 | this, 24 | () => { 25 | window.ReactNativeWebView.postMessage( 26 | JSON.stringify({ type: "OpenDrawer" }) 27 | ); 28 | }, 29 | EventType.TAP 30 | ); 31 | return _menuBtn; 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /app/function-overrides/sidebarnav-override.js: -------------------------------------------------------------------------------- 1 | import { AutoBuyerViewController } from "../views/AutoBuyerViewController"; 2 | 3 | export const sideBarNavOverride = () => { 4 | const navViewInit = UTGameTabBarController.prototype.initWithViewControllers; 5 | UTGameTabBarController.prototype.initWithViewControllers = function (tabs) { 6 | const autoBuyerNav = new UTGameFlowNavigationController(); 7 | autoBuyerNav.initWithRootController(new AutoBuyerViewController()); 8 | autoBuyerNav.tabBarItem = generateAutoBuyerTab("Autobuyer"); 9 | tabs = getApplicableTabs(tabs); 10 | tabs.push(autoBuyerNav); 11 | navViewInit.call(this, tabs); 12 | }; 13 | }; 14 | 15 | const getApplicableTabs = (tabs) => { 16 | if (!isPhone()) { 17 | return tabs; 18 | } 19 | 20 | const updatedTabs = []; 21 | updatedTabs.push(tabs[0]); 22 | updatedTabs.push(tabs[2]); 23 | updatedTabs.push(tabs[3]); 24 | updatedTabs.push(tabs[4]); 25 | 26 | return updatedTabs; 27 | }; 28 | 29 | const generateAutoBuyerTab = (title) => { 30 | const autoBuyerTab = new UTTabBarItemView(); 31 | autoBuyerTab.init(); 32 | autoBuyerTab.setTag(8); 33 | autoBuyerTab.setText(title); 34 | autoBuyerTab.addClass("icon-transfer"); 35 | return autoBuyerTab; 36 | }; 37 | -------------------------------------------------------------------------------- /app/index.js: -------------------------------------------------------------------------------- 1 | import { isMarketAlertApp } from "./app.constants"; 2 | import { initOverrides } from "./function-overrides"; 3 | import { cssOverride } from "./function-overrides/css-override"; 4 | import { initListeners } from "./services/listeners"; 5 | 6 | const initAutobuyer = function () { 7 | let isHomePageLoaded = false; 8 | isPhone() && $("body").removeClass("landscape").addClass("phone"); 9 | $(".ui-orientation-warning").attr("style", "display: none !important"); 10 | $(".ut-fifa-header-view").attr("style", "display: none !important"); 11 | if ( 12 | services.Localization && 13 | $("h1.title").html() === services.Localization.localize("navbar.label.home") 14 | ) { 15 | isHomePageLoaded = true; 16 | } 17 | 18 | if (isHomePageLoaded) { 19 | cssOverride(); 20 | } else { 21 | setTimeout(initAutobuyer, 1000); 22 | } 23 | }; 24 | 25 | const initFunctionOverrides = function () { 26 | let isPageLoaded = false; 27 | if (services.Localization) { 28 | isPageLoaded = true; 29 | } 30 | if (isPageLoaded) { 31 | initOverrides(); 32 | initAutobuyer(); 33 | isMarketAlertApp && initListeners(); 34 | } else { 35 | setTimeout(initFunctionOverrides, 1000); 36 | } 37 | }; 38 | initFunctionOverrides(); 39 | -------------------------------------------------------------------------------- /app/utils/processorUtil.js: -------------------------------------------------------------------------------- 1 | import { STATE_ACTIVE } from "../app.constants"; 2 | import { setValue } from "../services/repository"; 3 | import { pauseBotIfRequired, switchFilterIfRequired } from "./autoActionsUtil"; 4 | import { sendUINotification } from "./notificationUtil"; 5 | import { searchTransferMarket } from "./searchUtil"; 6 | import { transferListUtil } from "./transferlistUtil"; 7 | import { watchListUtil } from "./watchlistUtil"; 8 | 9 | export const setInitialValues = (isResume) => { 10 | sendUINotification(isResume ? "Autobuyer Resumed" : "Autobuyer Started"); 11 | setValue("autoBuyerActive", true); 12 | setValue("autoBuyerState", STATE_ACTIVE); 13 | isPhone() && $(".ut-tab-bar-item").attr("disabled", true); 14 | if (!isResume) { 15 | setValue("botStartTime", new Date()); 16 | setValue("purchasedCardCount", 0); 17 | setValue("searchFailedCount", 0); 18 | setValue("currentPage", 1); 19 | } 20 | }; 21 | 22 | export const getFunctionsWithContext = function () { 23 | return { 24 | switchFilterWithContext: switchFilterIfRequired.bind(this), 25 | srchTmWithContext: searchTransferMarket.bind(this), 26 | watchListWithContext: watchListUtil.bind(this), 27 | transferListWithContext: transferListUtil.bind(this), 28 | pauseBotWithContext: pauseBotIfRequired.bind(this), 29 | }; 30 | }; 31 | -------------------------------------------------------------------------------- /tampermonkey-header.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | headers: { 3 | name: "FUT Auto Buyer", 4 | namespace: "http://tampermonkey.net/", 5 | version: "1.6.2", 6 | description: "FUT Auto Buyer", 7 | author: "CK Algos", 8 | match: [ 9 | "https://www.ea.com/*/fifa/ultimate-team/web-app/*", 10 | "https://www.ea.com/fifa/ultimate-team/web-app/*", 11 | ], 12 | grant: ["GM_xmlhttpRequest"], 13 | connect: [ 14 | "ea.com", 15 | "ea2.com", 16 | "futbin.com", 17 | "futwiz.com", 18 | "discordapp.com", 19 | "futbin.org", 20 | "exp.host", 21 | "on.aws", 22 | ], 23 | require: [ 24 | "https://code.jquery.com/jquery-3.6.1.min.js", 25 | "https://raw.githubusercontent.com/ckalgos/FUT-Auto-Buyer/main/external/discord.11.4.2.min.js", 26 | "https://greasyfork.org/scripts/47911-font-awesome-all-js/code/Font-awesome%20AllJs.js?version=275337", 27 | "https://github.com/ckalgos/fut-trade-enhancer/releases/latest/download/fut-trade-enhancer.user.js", 28 | ], 29 | updateURL: 30 | "https://github.com/ckalgos/fut-auto-buyer/releases/latest/download/fut-auto-buyer.user.js", 31 | downloadURL: 32 | "https://github.com/ckalgos/fut-auto-buyer/releases/latest/download/fut-auto-buyer.user.js", 33 | noFrame: true, 34 | }, 35 | }; 36 | -------------------------------------------------------------------------------- /app/function-overrides/network-override.js: -------------------------------------------------------------------------------- 1 | const defaultFetch = window.fetch; 2 | window.fetch = function (request, options) { 3 | if ( 4 | request && 5 | (/discordapp/.test(request) || /exp.host/.test(request)) && 6 | (options.method === "POST" || options.method === "DELETE") 7 | ) { 8 | return new Promise((resolve, reject) => { 9 | const headers = Object.assign({}, options.headers, { 10 | "User-Agent": "From Node", 11 | }); 12 | GM_xmlhttpRequest({ 13 | method: options.method, 14 | headers, 15 | url: request, 16 | data: options.body, 17 | onload: (res) => { 18 | if (res.status === 200 || res.status === 204) { 19 | res.text = () => 20 | new Promise((resolve) => resolve(res.responseText)); 21 | res.headers = res.responseHeaders 22 | .split("\r\n") 23 | .reduce(function (acc, current) { 24 | var parts = current.split(": "); 25 | acc.set(parts[0], parts[1]); 26 | return acc; 27 | }, new Map()); 28 | resolve(res); 29 | } else { 30 | reject(res); 31 | } 32 | }, 33 | }); 34 | }); 35 | } 36 | const response = defaultFetch.call(this, request, options); 37 | return response; 38 | }; 39 | -------------------------------------------------------------------------------- /app/views/layouts/LogView.js: -------------------------------------------------------------------------------- 1 | import { idLog, idProgressAutobuyer } from "../../elementIds.constants"; 2 | import { clearLogs } from "../../utils/logUtil"; 3 | import { createButton } from "./ButtonView"; 4 | 5 | export const logView = () => { 6 | const logContainer = $(`
11 |
12 | 13 |
14 |
15 |
16 |
17 |
18 | 19 | Join Our Discord Server 20 |
21 |
`); 22 | const buttons = logContainer.find(".button-clear"); 23 | buttons.append(createButton("⎚", () => clearLogs()).__root); 24 | return logContainer; 25 | }; 26 | 27 | export const initializeLog = () => { 28 | const log = $("#" + idProgressAutobuyer); 29 | let time_txt = "[" + new Date().toLocaleTimeString() + "] "; 30 | let log_init_text = `Autobuyer Ready ${time_txt}\n`; 31 | log.val(log_init_text); 32 | }; 33 | -------------------------------------------------------------------------------- /app/utils/uiUtils/generateToggleInput.js: -------------------------------------------------------------------------------- 1 | import { getValue, setValue } from "../../services/repository"; 2 | let eventMappers = new Set(); 3 | 4 | const clickHandler = (key, settingKey, evt) => { 5 | const buyerSetting = getValue(settingKey) || {}; 6 | if (buyerSetting[key]) { 7 | buyerSetting[key] = false; 8 | $(evt.currentTarget).removeClass("toggled"); 9 | } else { 10 | buyerSetting[key] = true; 11 | $(evt.currentTarget).addClass("toggled"); 12 | } 13 | setValue(settingKey, buyerSetting); 14 | }; 15 | 16 | export const generateToggleInput = ( 17 | label, 18 | id, 19 | info, 20 | settingKey, 21 | additionalClasses = "buyer-settings-field", 22 | customCallBack = null 23 | ) => { 24 | const key = Object.keys(id)[0]; 25 | if (!eventMappers.has(key)) { 26 | $(document).on("click touchend", `#${id[key]}`, (evt) => { 27 | !customCallBack && clickHandler(key, settingKey, evt); 28 | customCallBack && customCallBack(evt); 29 | }); 30 | eventMappers.add(key); 31 | } 32 | return ` 33 |
34 |
35 | ${label}
${info}
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
`; 44 | }; 45 | -------------------------------------------------------------------------------- /app/views/layouts/Settings/SettingsVarId.js: -------------------------------------------------------------------------------- 1 | export const totalCardsToBuy = "totalCardsToBuy"; 2 | export const bidPrice = "bidPrice"; 3 | export const bidExpiringIn = "bidExpiringIn"; 4 | export const isBidExact = "isBidExact"; 5 | export const buyPrice = "buyPrice"; 6 | 7 | export const isCloseOnCaptchaTrigger = "isCloseOnCaptchaTrigger"; 8 | export const isAutoSolveCaptcha = "isAutoSolveCaptcha"; 9 | export const captchaKey = "captchaKey"; 10 | export const captchaProxy = "captchaProxy"; 11 | export const captchaProxyPort = "captchaProxyPort"; 12 | export const captchaProxyUserName = "captchaProxyUserName"; 13 | export const captchaProxyPassword = "captchaProxyPassword"; 14 | 15 | export const isAutoClearLog = "isAutoClearLog"; 16 | export const errorCodesToStop = "errorCodesToStop"; 17 | 18 | export const telegramBotToken = "telegramBotToken"; 19 | export const telegramChatId = "telegramChatId"; 20 | export const discordBotToken = "discordBotToken"; 21 | export const discordChannelId = "discordChannelId"; 22 | export const notificationType = "notificationType"; 23 | export const isSendNotification = "isSendNotification"; 24 | export const isToggleNotification = "isToggleNotification"; 25 | 26 | export const waittime = "waittime"; 27 | export const maxPurchasePerSearch = "maxPurchasePerSearch"; 28 | export const pauseCycle = "pauseCycle"; 29 | export const pauseFor = "pauseFor"; 30 | export const stopAfter = "stopAfter"; 31 | export const isAddDelay = "isAddDelay"; 32 | 33 | export const sellPrice = "sellPrice"; 34 | export const clearSoldCount = "clearSoldCount"; 35 | export const isRelistUnsold = "isRelistUnsold"; 36 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/custom.yml: -------------------------------------------------------------------------------- 1 | name: 🚫 Ban Report 2 | description: If you got banned for using the script, let us know with the settings you used in the bot 3 | title: "ban: " 4 | labels: ["ban"] 5 | body: 6 | - type: checkboxes 7 | attributes: 8 | label: Prerequisites 9 | description: Please ensure you have completed all of the following. 10 | options: 11 | - label: I havent used any other script which could have caused the ban. 12 | required: true 13 | - type: input 14 | attributes: 15 | label: Script Version 16 | description: Please select which version of the script. 17 | validations: 18 | required: true 19 | - type: dropdown 20 | attributes: 21 | label: Platform 22 | description: Please select the platforms. 23 | multiple: true 24 | options: 25 | - Webapp 26 | - Android 27 | - ios 28 | validations: 29 | required: true 30 | - type: textarea 31 | attributes: 32 | label: Safety Settings 33 | description: Please share the Safety settings used when running the bot. 34 | validations: 35 | required: true 36 | - type: textarea 37 | attributes: 38 | label: Filter Used 39 | description: Please download the filter used when running the bot and share it here. 40 | validations: 41 | required: false 42 | - type: textarea 43 | attributes: 44 | label: Additional Information 45 | description: List any other information that is relevant to your issue. Browser version, related issues, suggestions on how to fix, console error screenshot, etc. 46 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: 💡 Feature Request 2 | description: Suggest an idea to improve the script 3 | title: "feat: " 4 | labels: ["enhancement"] 5 | body: 6 | - type: checkboxes 7 | attributes: 8 | label: Prerequisites 9 | description: Please ensure you have completed all of the following. 10 | options: 11 | - label: I have searched for [existing issues](https://github.com/ckalgos/FUT-Auto-Buyer/issues) that already include this feature request, without success. 12 | required: true 13 | - type: textarea 14 | attributes: 15 | label: Describe the Feature Request 16 | description: A clear and concise description of what the feature does. 17 | validations: 18 | required: true 19 | - type: textarea 20 | attributes: 21 | label: Describe the Use Case 22 | description: A clear and concise use case for what problem this feature would solve. 23 | validations: 24 | required: true 25 | - type: textarea 26 | attributes: 27 | label: Describe Preferred Solution 28 | description: A clear and concise description of what you how you want this feature to be added to the script. 29 | - type: textarea 30 | attributes: 31 | label: Describe Alternatives 32 | description: A clear and concise description of any alternative solutions or features you have considered. 33 | - type: textarea 34 | attributes: 35 | label: Additional Information 36 | description: List any other information that is relevant to your feature request. Any other script reference, suggestions on how to implement, forum links, etc. 37 | -------------------------------------------------------------------------------- /app/utils/uiUtils/generateTextInput.js: -------------------------------------------------------------------------------- 1 | import { getValue, setValue } from "../../services/repository"; 2 | let eventMappers = new Set(); 3 | 4 | const updateCache = (key, settingKey, value, type, isDefaultValue = false) => { 5 | const buyerSetting = getValue(settingKey) || {}; 6 | if (type === "number") value = parseInt(value); 7 | buyerSetting[key] = value || null; 8 | buyerSetting[key + "isDefaultValue"] = isDefaultValue; 9 | setValue(settingKey, buyerSetting); 10 | }; 11 | 12 | export const generateTextInput = ( 13 | label, 14 | placeholder, 15 | id, 16 | info, 17 | settingKey, 18 | type = "number", 19 | pattern = ".*", 20 | additionalClasses = "buyer-settings-field", 21 | customCallBack = null 22 | ) => { 23 | const key = Object.keys(id)[0]; 24 | if (placeholder) { 25 | customCallBack && customCallBack(placeholder); 26 | updateCache(key, settingKey, placeholder, type, true); 27 | } 28 | if (!eventMappers.has(key)) { 29 | $(document).on("input", `#${id[key]}`, ({ target: { value } }) => { 30 | customCallBack && customCallBack(value); 31 | updateCache(key, settingKey, value || placeholder, type, !value); 32 | }); 33 | eventMappers.add(key); 34 | } 35 | return `
36 |
37 | ${label} :
${info}
38 |
39 |
40 |
41 | 42 |
43 |
44 |
`; 45 | }; 46 | -------------------------------------------------------------------------------- /app/utils/logUtil.js: -------------------------------------------------------------------------------- 1 | import { idProgressAutobuyer } from "../elementIds.constants"; 2 | import { getBuyerSettings } from "../services/repository"; 3 | import { initializeLog } from "../views/layouts/LogView"; 4 | import { sendNotificationToUser } from "./notificationUtil"; 5 | 6 | export const writeToAbLog = ( 7 | sym, 8 | ItemName, 9 | priceTxt, 10 | operation, 11 | result, 12 | comments 13 | ) => { 14 | writeToLog( 15 | sym + 16 | " | " + 17 | ItemName + 18 | " | " + 19 | priceTxt + 20 | " | " + 21 | operation + 22 | " | " + 23 | result + 24 | " | " + 25 | comments, 26 | idProgressAutobuyer 27 | ); 28 | }; 29 | 30 | export const showCaptchaLogs = function (captchaCloseTab) { 31 | sendNotificationToUser( 32 | "Captcha, please solve the problem so that the bot can work again.", 33 | false 34 | ); 35 | 36 | if (captchaCloseTab) { 37 | window.location.href = "about:blank"; 38 | return; 39 | } 40 | writeToLog( 41 | "[!!!] Autostopping bot since Captcha got triggered", 42 | idProgressAutobuyer 43 | ); 44 | }; 45 | 46 | export const writeToLog = function (message, log) { 47 | setTimeout(() => { 48 | var $log = $("#" + log); 49 | message = "[" + new Date().toLocaleTimeString() + "] " + message + "\n"; 50 | $log.val($log.val() + message); 51 | if ($log[0]) $log.scrollTop($log[0].scrollHeight); 52 | }, 50); 53 | }; 54 | 55 | export const clearLogs = () => { 56 | $("#" + idProgressAutobuyer).val(""); 57 | initializeLog(); 58 | }; 59 | 60 | setInterval(() => { 61 | const settings = getBuyerSettings(); 62 | let autoClearLog = settings && settings["idAutoClearLog"]; 63 | autoClearLog && clearLogs(); 64 | }, 120000); 65 | -------------------------------------------------------------------------------- /app/views/layouts/Settings/CaptchaSettingsView.js: -------------------------------------------------------------------------------- 1 | import { 2 | idAbCloseTabToggle, 3 | idAbSolveCaptcha, 4 | idAntiCaptchKey, 5 | idProxyAddress, 6 | idProxyLogin, 7 | idProxyPassword, 8 | idProxyPort, 9 | } from "../../../elementIds.constants"; 10 | import { generateTextInput } from "../../../utils/uiUtils/generateTextInput"; 11 | import { generateToggleInput } from "../../../utils/uiUtils/generateToggleInput"; 12 | 13 | export const captchaSettingsView = function () { 14 | return `