`);
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 |
`;
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 `
`;
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 `
15 | ${generateToggleInput(
16 | "Close Web App on Captcha Trigger",
17 | { idAbCloseTabToggle },
18 | "",
19 | "CommonSettings"
20 | )}
21 | ${generateToggleInput(
22 | "Auto Solve Captcha",
23 | { idAbSolveCaptcha },
24 | "",
25 | "CommonSettings"
26 | )}
27 | ${generateTextInput(
28 | "Anti-Captcha Key",
29 | "",
30 | { idAntiCaptchKey },
31 | "",
32 | "CommonSettings",
33 | "text"
34 | )}
35 | ${generateTextInput(
36 | "Proxy Address",
37 | "",
38 | { idProxyAddress },
39 | "",
40 | "CommonSettings",
41 | "text"
42 | )}
43 | ${generateTextInput(
44 | "Proxy Port",
45 | "",
46 | { idProxyPort },
47 | "",
48 | "CommonSettings"
49 | )}
50 | ${generateTextInput(
51 | "Proxy User Name (Optional)",
52 | "",
53 | { idProxyLogin },
54 | "",
55 | "CommonSettings",
56 | "text"
57 | )}
58 | ${generateTextInput(
59 | "Proxy User Password (Optional)",
60 | "",
61 | { idProxyPassword },
62 | "",
63 | "CommonSettings",
64 | "text"
65 | )}
66 | `;
67 | };
68 |
--------------------------------------------------------------------------------
/app/views/layouts/Settings/CommonSettingsView.js:
--------------------------------------------------------------------------------
1 | import {
2 | idAbStopErrorCode,
3 | idAutoClearLog,
4 | idAbStopErrorCodeCount,
5 | idAutoClearExpired,
6 | idAbResumeAfterErrorOccured,
7 | idAbUseFutWiz,
8 | } from "../../../elementIds.constants";
9 | import { generateTextInput } from "../../../utils/uiUtils/generateTextInput";
10 | import { generateToggleInput } from "../../../utils/uiUtils/generateToggleInput";
11 |
12 | export const commonSettingsView = function () {
13 | return `
14 | ${generateTextInput(
15 | "Error Codes to stop bot (csv)",
16 | "",
17 | { idAbStopErrorCode },
18 | "(Eg. 412,421,521)",
19 | "CommonSettings",
20 | "text",
21 | "^\\d+(,\\d+)*$"
22 | )}
23 | ${generateTextInput(
24 | "No. of times error code should occur",
25 | 3,
26 | { idAbStopErrorCodeCount },
27 | " ",
28 | "CommonSettings"
29 | )}
30 | ${generateTextInput(
31 | "Resume bot after",
32 | "",
33 | { idAbResumeAfterErrorOccured },
34 | "(S for seconds, M for Minutes, H for hours eg. 0-0S)",
35 | "CommonSettings",
36 | "text",
37 | "\\d+-\\d+[H|M|S|h|m|s]$"
38 | )}
39 | ${generateToggleInput(
40 | "Auto Clear Log",
41 | { idAutoClearLog },
42 | "(Automatically clear logs every 2 minutes)",
43 | "CommonSettings"
44 | )}
45 | ${generateToggleInput(
46 | "Auto Clear Expired Items",
47 | { idAutoClearExpired },
48 | "(Automatically clear expired items from transfer targets)",
49 | "CommonSettings"
50 | )}
51 | ${generateToggleInput(
52 | "Use Futwiz Price",
53 | { idAbUseFutWiz },
54 | "(Uses Futwiz price for buying/selling cards)",
55 | "CommonSettings"
56 | )}
57 |
`;
58 | };
59 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.yml:
--------------------------------------------------------------------------------
1 | name: 🐛 Bug Report
2 | description: Create a report to help us improve the script
3 | title: "bug: "
4 | labels: ["bug"]
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 report this problem, without success.
12 | required: true
13 | - type: input
14 | attributes:
15 | label: Script Version
16 | description: Please select which version of script has this issue impacts.
17 | validations:
18 | required: true
19 | - type: dropdown
20 | attributes:
21 | label: Platform
22 | description: Please select the platforms on which you are facing this issue.
23 | multiple: true
24 | options:
25 | - Webapp
26 | - Android
27 | - ios
28 | validations:
29 | required: true
30 | - type: textarea
31 | attributes:
32 | label: Current Behavior
33 | description: A clear description of what the bug is and how it manifests.
34 | validations:
35 | required: true
36 | - type: textarea
37 | attributes:
38 | label: Expected Behavior
39 | description: A clear description of what you expected to happen.
40 | validations:
41 | required: true
42 | - type: textarea
43 | attributes:
44 | label: Steps to Reproduce
45 | description: Please explain the steps required to duplicate this issue.
46 | validations:
47 | required: true
48 | - type: textarea
49 | attributes:
50 | label: Additional Information
51 | description: List any other information that is relevant to your issue. Browser version, related issues, suggestions on how to fix, console error screenshot, etc.
52 |
--------------------------------------------------------------------------------
/app/handlers/errorHandler.js:
--------------------------------------------------------------------------------
1 | import { idProgressAutobuyer } from "../elementIds.constants";
2 | import {
3 | getBuyerSettings,
4 | increAndGetStoreValue,
5 | } from "../services/repository";
6 | import { playAudio } from "../utils/commonUtil";
7 | import { showCaptchaLogs, writeToLog } from "../utils/logUtil";
8 | import { sendNotificationToUser } from "../utils/notificationUtil";
9 | import { stopAutoBuyer } from "./autobuyerProcessor";
10 | import { solveCaptcha } from "./captchaSolver";
11 |
12 | export const searchErrorHandler = (
13 | response,
14 | canSolveCaptcha,
15 | captchaCloseTab
16 | ) => {
17 | let shouldStopBot = false;
18 | if (
19 | response.status === UtasErrorCode.CAPTCHA_REQUIRED ||
20 | (response.error && response.error.code == UtasErrorCode.CAPTCHA_REQUIRED)
21 | ) {
22 | shouldStopBot = true;
23 | if (canSolveCaptcha) {
24 | writeToLog(
25 | "[!!!] Captcha got triggered, trying to solve it",
26 | idProgressAutobuyer
27 | );
28 | solveCaptcha();
29 | } else {
30 | showCaptchaLogs(captchaCloseTab);
31 | }
32 | } else {
33 | const searchFailedCount = increAndGetStoreValue("searchFailedCount");
34 | if (searchFailedCount >= 3) {
35 | shouldStopBot = true;
36 | writeToLog(
37 | `[!!!] Autostopping bot as search failed for ${searchFailedCount} consecutive times, please check if you can access transfer market in Web App ${response.status}`,
38 | idProgressAutobuyer
39 | );
40 | } else {
41 | writeToLog(
42 | `[!!!] Search failed - ${response.status}`,
43 | idProgressAutobuyer
44 | );
45 | }
46 | }
47 | if (shouldStopBot) {
48 | playAudio("capatcha");
49 | const buyerSetting = getBuyerSettings();
50 | buyerSetting["idNotificationType"] === "A" &&
51 | sendNotificationToUser("Autobuyer Stopped", false);
52 |
53 | stopAutoBuyer();
54 | }
55 | };
56 |
--------------------------------------------------------------------------------
/app/utils/statsUtil.js:
--------------------------------------------------------------------------------
1 | import { getValue, setValue } from "../services/repository";
2 | import { downloadCsv } from "./commonUtil";
3 |
4 | export const updateRequestCount = () => {
5 | const currentStats = getValue("sessionStats");
6 | currentStats["searchCount"]++;
7 | currentStats["searchPerMinuteCount"]++;
8 | setValue("sessionStats", currentStats);
9 | };
10 |
11 | export const updateProfit = (val) => {
12 | const currentStats = getValue("sessionStats");
13 | currentStats["profit"] += val;
14 | setValue("sessionStats", currentStats);
15 | };
16 |
17 | export const resetProfit = () => {
18 | const currentStats = getValue("sessionStats");
19 | currentStats["profit"] = 0;
20 | setValue("sessionStats", currentStats);
21 | };
22 |
23 | export const appendTransactions = (val) => {
24 | const currentStats = getValue("sessionStats");
25 | currentStats["transactions"] = currentStats["transactions"] || [];
26 | currentStats["transactions"].push(val);
27 | setValue("sessionStats", currentStats);
28 | };
29 |
30 | export const downloadStats = () => {
31 | const { coinsNumber, searchCount, profit, runningTime, transactions } =
32 | getValue("sessionStats");
33 | const winCount = getValue("winCount");
34 | const bidCount = getValue("bidCount");
35 | const lossCount = getValue("lossCount");
36 | let csvContent =
37 | "Available Coins,Search Count,Profit,Running Time,BIN Won Count,BID Won Count,Loss Count\n";
38 | csvContent += `${coinsNumber || ""},${searchCount || ""},${profit || 0},${
39 | runningTime || ""
40 | },${winCount || 0},${bidCount || 0},${lossCount || 0}\n\n`;
41 | csvContent += `Transactions\n`;
42 | csvContent += transactions.map((transact) => `${transact}\n`).join("");
43 | downloadCsv(csvContent, "Stats");
44 | };
45 |
46 | setInterval(() => {
47 | const currentStats = getValue("sessionStats");
48 | currentStats["searchPerMinuteCount"] = 0;
49 | setValue("sessionStats", currentStats);
50 | }, 55000);
51 |
--------------------------------------------------------------------------------
/app/app.constants.js:
--------------------------------------------------------------------------------
1 | export const MAX_CLUB_SEARCH = 90;
2 | export const MAX_MARKET_SEARCH = 20;
3 |
4 | export const STATE_ACTIVE = "Active";
5 | export const STATE_PAUSED = "Paused";
6 | export const STATE_STOPPED = "Stopped";
7 |
8 | export const errorCodeLookUp = {
9 | 521: "Request Rejected",
10 | 512: "Request Rejected",
11 | 429: "Too many request from this user",
12 | 426: "Other user won the (card / bid)",
13 | 461: "Other user won the (card / bid)",
14 | };
15 |
16 | export const defaultBuyerSetting = {
17 | idBuyFutBinPercent: 95,
18 | idAbCardCount: 1000,
19 | idAbCardCountisDefaultValue: true,
20 | idAbItemExpiring: "1H",
21 | idAbItemExpiringisDefaultValue: true,
22 | idAbSearchResult: 21,
23 | idAbSearchResultisDefaultValue: true,
24 | idSellFutBinPercent: "105-110",
25 | idFutBinDuration: "1H",
26 | idFutBinDurationisDefaultValue: true,
27 | idAbMinDeleteCount: 10,
28 | idSellRatingThreshold: 100,
29 | idSellRatingThresholdisDefaultValue: true,
30 | idAbMinRating: 10,
31 | idAbMinRatingisDefaultValue: true,
32 | idAbMaxRating: 100,
33 | idAbMaxRatingisDefaultValue: true,
34 | idAbMaxSearchPage: 5,
35 | idAbMaxSearchPageisDefaultValue: true,
36 | idAbRandMinBidInput: 300,
37 | idAbRandMinBidInputisDefaultValue: true,
38 | idAbRandMinBuyInput: 300,
39 | idAbRandMinBuyInputisDefaultValue: true,
40 | idBuyFutBinPrice: true,
41 | idSellFutBinPrice: true,
42 | idSellCheckBuyPrice: true,
43 | idAbSellToggle: false,
44 | idAbRandMinBidToggle: true,
45 | };
46 |
47 | export const defaultCommonSetting = {
48 | idAbAddBuyDelay: true,
49 | idAbCycleAmount: "15-20",
50 | idAbDelayToAdd: "3S",
51 | idAbMaxPurchases: 1,
52 | idAbNumberFilterSearch: 3,
53 | idAbNumberFilterSearchisDefaultValue: true,
54 | idAbPauseFor: "15-30S",
55 | idAbSoundToggle: true,
56 | idAbStopAfter: "1-2H",
57 | idAbStopErrorCodeCount: 3,
58 | idAbWaitTime: "5-8",
59 | idAbWaitTimeisDefaultValue: false,
60 | idAutoClearExpired: true,
61 | idAutoClearLog: true,
62 | };
63 |
64 | export const isMarketAlertApp = !!window.ReactNativeWebView;
65 |
--------------------------------------------------------------------------------
/app/utils/phoneDbUtil.js:
--------------------------------------------------------------------------------
1 | let db;
2 |
3 | const initDatabase = () => {
4 | let indexedDB =
5 | window.indexedDB ||
6 | window.mozIndexedDB ||
7 | window.webkitIndexedDB ||
8 | window.msIndexedDB ||
9 | window.shimIndexedDB;
10 |
11 | let request = indexedDB.open("userDatasAB", 2);
12 |
13 | request.onupgradeneeded = function (e) {
14 | db = request.result;
15 | try {
16 | if (e.oldVersion < 1) {
17 | db.createObjectStore("Filters", { keyPath: "filterName" });
18 | }
19 | if (e.oldVersion < 2) {
20 | db.createObjectStore("CommonSettings", { keyPath: "filterName" });
21 | }
22 | } catch (e) {}
23 | };
24 |
25 | request.onsuccess = function () {
26 | db = request.result;
27 | };
28 | };
29 |
30 | const getUserFilters = (storeName = "Filters") => {
31 | return new Promise((resolve, reject) => {
32 | const tx = db.transaction(storeName, "readonly");
33 | const store = tx.objectStore(storeName);
34 | const request = store.getAll();
35 | request.onsuccess = function () {
36 | const filters = {};
37 | if (request.result.length) {
38 | for (let i = 0; i < request.result.length; i++) {
39 | filters[request.result[i].filterName] = request.result[i].jsonData;
40 | }
41 | }
42 | resolve(filters);
43 | };
44 | request.onerror = function () {
45 | reject();
46 | };
47 | });
48 | };
49 |
50 | const insertFilters = (filterName, jsonData, storeName = "Filters") => {
51 | return new Promise((resolve, reject) => {
52 | const tx = db.transaction(storeName, "readwrite");
53 | const store = tx.objectStore(storeName);
54 | store.put({ filterName, jsonData });
55 |
56 | tx.oncomplete = function () {
57 | resolve();
58 | };
59 | tx.onerror = function () {
60 | reject();
61 | };
62 | });
63 | };
64 |
65 | const deleteFilters = (filterName) => {
66 | return new Promise((resolve, reject) => {
67 | const tx = db.transaction("Filters", "readwrite");
68 | const store = tx.objectStore("Filters");
69 | store.delete(filterName);
70 | tx.oncomplete = function () {
71 | resolve();
72 | };
73 | tx.onerror = function () {
74 | reject();
75 | };
76 | });
77 | };
78 |
79 | export default {
80 | initDatabase,
81 | deleteFilters,
82 | insertFilters,
83 | getUserFilters,
84 | };
85 |
--------------------------------------------------------------------------------
/app/views/layouts/Settings/SafeSettingsView.js:
--------------------------------------------------------------------------------
1 | import {
2 | idAbCycleAmount,
3 | idAbPauseFor,
4 | idAbStopAfter,
5 | idAbWaitTime,
6 | idAbAddBuyDelay,
7 | idAbDelayToAdd,
8 | idAbOverSearchWarning,
9 | idAbMaxPurchases,
10 | } from "../../../elementIds.constants";
11 | import { generateTextInput } from "../../../utils/uiUtils/generateTextInput";
12 | import { generateToggleInput } from "../../../utils/uiUtils/generateToggleInput";
13 |
14 | export const safeSettingsView = function () {
15 | return `
16 | ${generateTextInput(
17 | "Wait Time",
18 | "7-15",
19 | { idAbWaitTime },
20 | "(Random second range eg. 7-15)",
21 | "CommonSettings",
22 | "text",
23 | "\\d+-\\d+$"
24 | )}
25 | ${generateTextInput(
26 | "Max purchases per search request",
27 | 1,
28 | { idAbMaxPurchases },
29 | "Increase this, only if you are Adding Delay After Buy of alteast 3S",
30 | "CommonSettings"
31 | )}
32 | ${generateTextInput(
33 | "Pause Cycle",
34 | "10-15",
35 | { idAbCycleAmount },
36 | "(No. of searches performed before triggering Pause eg. 10-15)",
37 | "CommonSettings",
38 | "text",
39 | "\\d+-\\d+$"
40 | )}
41 | ${generateTextInput(
42 | "Pause For",
43 | "5-8S",
44 | { idAbPauseFor },
45 | "(S for seconds, M for Minutes, H for hours eg. 0-0S)",
46 | "CommonSettings",
47 | "text",
48 | "\\d+-\\d+[H|M|S|h|m|s]$"
49 | )}
50 | ${generateToggleInput(
51 | "Add Delay After Buy",
52 | { idAbAddBuyDelay },
53 | "(Adds Delay after trying to buy / bid a card)",
54 | "CommonSettings"
55 | )}
56 | ${generateTextInput(
57 | "Delay To Add",
58 | "1S",
59 | { idAbDelayToAdd },
60 | "(S for seconds, M for Minutes, H for hours)",
61 | "CommonSettings",
62 | "text",
63 | "\\d+[H|M|S|h|m|s]$"
64 | )}
65 | ${generateTextInput(
66 | "Stop After",
67 | "3-4H",
68 | { idAbStopAfter },
69 | "(S for seconds, M for Minutes, H for hours eg. 3-4H)",
70 | "CommonSettings",
71 | "text",
72 | "\\d+-\\d+[H|M|S|h|m|s]$"
73 | )}
74 | ${generateToggleInput(
75 | "Show Search Exceed Warning",
76 | { idAbOverSearchWarning },
77 | "(Shows a warning in log if number of search per minute exceeds 15)",
78 | "CommonSettings"
79 | )}
80 |
`;
81 | };
82 |
--------------------------------------------------------------------------------
/app/views/layouts/Settings/BuySettingsView.js:
--------------------------------------------------------------------------------
1 | import {
2 | idAbBidExact,
3 | idAbBuyPrice,
4 | idAbCardCount,
5 | idAbItemExpiring,
6 | idAbMaxBid,
7 | idAbSearchResult,
8 | idBuyFutBinPrice,
9 | idAbBidFutBin,
10 | idBuyFutBinPercent,
11 | } from "../../../elementIds.constants";
12 | import { getDataSource } from "../../../services/repository";
13 | import { generateTextInput } from "../../../utils/uiUtils/generateTextInput";
14 | import { generateToggleInput } from "../../../utils/uiUtils/generateToggleInput";
15 |
16 | export const buySettingsView = function () {
17 | const dataSource = getDataSource();
18 | return `
19 | ${generateToggleInput(
20 | "Find Buy Price",
21 | { idBuyFutBinPrice },
22 | `(Uses ${dataSource} price for Buy)`,
23 | "BuyerSettings"
24 | )}
25 | ${generateTextInput(
26 | "Buy/Bid Price Percent",
27 | 100,
28 | { idBuyFutBinPercent },
29 | `(Buy/Bid Price percent of ${dataSource} Price)`,
30 | "BuyerSettings"
31 | )}
32 | ${generateToggleInput(
33 | `Bid For ${dataSource} Price`,
34 | { idAbBidFutBin },
35 | `(Bid if the current bid is lesser than ${dataSource} Price)`,
36 | "BuyerSettings"
37 | )}
38 | ${generateTextInput(
39 | "Buy Price",
40 | "",
41 | { idAbBuyPrice },
42 | " ",
43 | "BuyerSettings"
44 | )}
45 | ${generateTextInput(
46 | "No. of cards to buy",
47 | 1000,
48 | { idAbCardCount },
49 | "(Works only with Buy price)",
50 | "BuyerSettings"
51 | )}
52 | ${generateTextInput(
53 | "Bid Price",
54 | "",
55 | { idAbMaxBid },
56 | " ",
57 | "BuyerSettings"
58 | )}
59 | ${generateTextInput(
60 | "Bid items expiring in",
61 | "1H",
62 | { idAbItemExpiring },
63 | "(S for seconds, M for Minutes, H for hours)",
64 | "BuyerSettings",
65 | "text",
66 | "\\d+[H|M|S|h|m|s]$"
67 | )}
68 | ${generateTextInput(
69 | "Search result threshold",
70 | 21,
71 | { idAbSearchResult },
72 | "(Buy or bid cards only if the no.of search results is lesser than the specified value)",
73 | "BuyerSettings"
74 | )}
75 | ${generateToggleInput(
76 | "Bid Exact Price",
77 | { idAbBidExact },
78 | "",
79 | "BuyerSettings"
80 | )}
81 |
82 | `;
83 | };
84 |
--------------------------------------------------------------------------------
/.github/workflows/codeql-analysis.yml:
--------------------------------------------------------------------------------
1 | # For most projects, this workflow file will not need changing; you simply need
2 | # to commit it to your repository.
3 | #
4 | # You may wish to alter this file to override the set of languages analyzed,
5 | # or to provide custom queries or build logic.
6 | #
7 | # ******** NOTE ********
8 | # We have attempted to detect the languages in your repository. Please check
9 | # the `language` matrix defined below to confirm you have the correct set of
10 | # supported CodeQL languages.
11 | #
12 | name: "CodeQL"
13 |
14 | on:
15 | push:
16 | branches: [ master ]
17 | pull_request:
18 | # The branches below must be a subset of the branches above
19 | branches: [ master ]
20 | schedule:
21 | - cron: '27 3 * * 2'
22 |
23 | jobs:
24 | analyze:
25 | name: Analyze
26 | runs-on: ubuntu-latest
27 | permissions:
28 | actions: read
29 | contents: read
30 | security-events: write
31 |
32 | strategy:
33 | fail-fast: false
34 | matrix:
35 | language: [ 'javascript' ]
36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
37 | # Learn more about CodeQL language support at https://git.io/codeql-language-support
38 |
39 | steps:
40 | - name: Checkout repository
41 | uses: actions/checkout@v2
42 |
43 | # Initializes the CodeQL tools for scanning.
44 | - name: Initialize CodeQL
45 | uses: github/codeql-action/init@v1
46 | with:
47 | languages: ${{ matrix.language }}
48 | # If you wish to specify custom queries, you can do so here or in a config file.
49 | # By default, queries listed here will override any specified in a config file.
50 | # Prefix the list here with "+" to use these queries and those in the config file.
51 | # queries: ./path/to/local/query, your-org/your-repo/queries@main
52 |
53 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
54 | # If this step fails, then you should remove it and run the build manually (see below)
55 | - name: Autobuild
56 | uses: github/codeql-action/autobuild@v1
57 |
58 | # ℹ️ Command-line programs to run using the OS shell.
59 | # 📚 https://git.io/JvXDl
60 |
61 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
62 | # and modify them (or add more) to build your code if your project
63 | # uses a compiled language
64 |
65 | #- run: |
66 | # make bootstrap
67 | # make release
68 |
69 | - name: Perform CodeQL Analysis
70 | uses: github/codeql-action/analyze@v1
71 |
--------------------------------------------------------------------------------
/app/utils/webDbUtil.js:
--------------------------------------------------------------------------------
1 | let db;
2 | const initDatabase = () => {
3 | db = openDatabase("userDatas", "1.0", "FIFA AUTO BUYER DB", 2 * 1024 * 1024);
4 |
5 | db.transaction(function (tx) {
6 | tx.executeSql(
7 | `CREATE TABLE IF NOT EXISTS Filters (filterName,settings);`,
8 | [],
9 | function (tx, results) {
10 | tx.executeSql(
11 | `CREATE UNIQUE INDEX idx_filters_filterName ON Filters (filterName);`
12 | );
13 | }
14 | );
15 | });
16 |
17 | db.transaction(function (tx) {
18 | tx.executeSql(
19 | `CREATE TABLE IF NOT EXISTS CommonSettings (filterName,settings);`,
20 | [],
21 | function (tx, results) {
22 | tx.executeSql(
23 | `CREATE UNIQUE INDEX idx_commonsettings_filterName ON CommonSettings (filterName);`
24 | );
25 | }
26 | );
27 | });
28 | };
29 |
30 | const getUserFilters = (tableName = "Filters") => {
31 | return new Promise((resolve, reject) => {
32 | db.transaction(function (tx) {
33 | tx.executeSql(
34 | `SELECT * FROM ${tableName}`,
35 | [],
36 | function (tx, results) {
37 | const filters = {};
38 | if (results.rows.length) {
39 | for (let i = 0; i < results.rows.length; i++) {
40 | filters[results.rows[i].filterName] = results.rows[i].settings;
41 | }
42 | }
43 | resolve(filters);
44 | },
45 | function (tx, results) {
46 | reject(results);
47 | }
48 | );
49 | });
50 | });
51 | };
52 |
53 | const insertFilters = (filterName, jsonData, tableName = "Filters") => {
54 | return new Promise((resolve, reject) => {
55 | db.transaction(function (tx) {
56 | tx.executeSql(
57 | `REPLACE INTO ${tableName}(filterName,settings) Values (?,?)`,
58 | [filterName, jsonData],
59 | function (tx, results) {
60 | resolve(true);
61 | },
62 | function (tx, results) {
63 | reject(results);
64 | }
65 | );
66 | });
67 | });
68 | };
69 |
70 | const deleteFilters = (filterName) => {
71 | return new Promise((resolve, reject) => {
72 | db.transaction(function (tx) {
73 | tx.executeSql(
74 | "DELETE FROM Filters WHERE filterName=?",
75 | [filterName],
76 | function (tx, results) {
77 | resolve(true);
78 | },
79 | function (tx, results) {
80 | reject(results);
81 | }
82 | );
83 | });
84 | });
85 | };
86 |
87 | export default {
88 | initDatabase,
89 | deleteFilters,
90 | insertFilters,
91 | getUserFilters,
92 | };
93 |
--------------------------------------------------------------------------------
/app/utils/futbinUtil.js:
--------------------------------------------------------------------------------
1 | import { idProgressAutobuyer } from "../elementIds.constants";
2 | import { fetchPrices } from "../services/datasource";
3 | import { getDataSource, getValue } from "../services/repository";
4 | import { getRandNumberInRange } from "./commonUtil";
5 | import { writeToLog } from "./logUtil";
6 | import { getBuyBidPrice, roundOffPrice } from "./priceUtils";
7 |
8 | export const getSellPriceFromFutBin = async (
9 | buyerSetting,
10 | playerName,
11 | player
12 | ) => {
13 | let sellPrice;
14 | try {
15 | const definitionId = player.definitionId;
16 | const dataSource = getDataSource();
17 | if (player.type !== "player") {
18 | return sellPrice;
19 | }
20 | await fetchPrices([player]);
21 | const existingValue = getValue(
22 | `${definitionId}_${dataSource.toLowerCase()}_price`
23 | );
24 | if (existingValue && existingValue.price) {
25 | sellPrice = existingValue.price;
26 | const futBinPercent =
27 | getRandNumberInRange(buyerSetting["idSellFutBinPercent"]) || 100;
28 | let calculatedPrice = (sellPrice * futBinPercent) / 100;
29 | await getPriceLimits(player);
30 | if (player.hasPriceLimits()) {
31 | calculatedPrice = roundOffPrice(
32 | Math.min(
33 | player._itemPriceLimits.maximum,
34 | Math.max(player._itemPriceLimits.minimum, calculatedPrice)
35 | )
36 | );
37 |
38 | if (calculatedPrice === player._itemPriceLimits.minimum) {
39 | calculatedPrice = getBuyBidPrice(calculatedPrice);
40 | }
41 | }
42 | calculatedPrice = roundOffPrice(calculatedPrice);
43 | writeToLog(
44 | `= ${dataSource} price for ${playerName}: ${sellPrice}: ${futBinPercent}% of sale price: ${calculatedPrice}`,
45 | idProgressAutobuyer
46 | );
47 | sellPrice = calculatedPrice;
48 | } else {
49 | sellPrice = null;
50 | writeToLog(
51 | `= Unable to get ${dataSource} price for ${playerName}`,
52 | idProgressAutobuyer
53 | );
54 | }
55 | } catch (err) {
56 | err = err.statusText || err.status || err;
57 | sellPrice = null;
58 | writeToLog(
59 | `= Unable to get Futbin price for ${playerName}, err: ${
60 | err || "error occured"
61 | }`,
62 | idProgressAutobuyer
63 | );
64 | }
65 | return sellPrice;
66 | };
67 |
68 | const getPriceLimits = async (player) => {
69 | return new Promise((resolve) => {
70 | if (player.hasPriceLimits()) {
71 | resolve();
72 | return;
73 | }
74 | services.Item.requestMarketData(player).observe(
75 | this,
76 | async function (sender, response) {
77 | resolve();
78 | }
79 | );
80 | });
81 | };
82 |
--------------------------------------------------------------------------------
/app/utils/transferlistUtil.js:
--------------------------------------------------------------------------------
1 | import { idProgressAutobuyer } from "../elementIds.constants";
2 | import { getStatsValue, updateStats } from "../handlers/statsProcessor";
3 | import { writeToLog } from "./logUtil";
4 | import { sendPinEvents } from "./notificationUtil";
5 | import { updateUserCredits } from "./userUtil";
6 |
7 | export const transferListUtil = function (
8 | relistUnsold,
9 | minSoldCount,
10 | isInitialRun
11 | ) {
12 | sendPinEvents("Transfer List - List View");
13 | return new Promise((resolve) => {
14 | if (!isInitialRun && !repositories.Item.isDirty(ItemPile["TRANSFER"])) {
15 | resolve();
16 | return;
17 | }
18 | services.Item.requestTransferItems().observe(
19 | this,
20 | async function (t, response) {
21 | let soldItems = response.response.items.filter(function (item) {
22 | return item.getAuctionData().isSold();
23 | }).length;
24 | if (getStatsValue("soldItems") < soldItems) {
25 | await updateUserCredits();
26 | }
27 | updateStats("soldItems", soldItems);
28 |
29 | const unsoldItems = response.response.items.filter(function (item) {
30 | return (
31 | !item.getAuctionData().isSold() && item.getAuctionData().isExpired()
32 | );
33 | }).length;
34 | updateStats("unsoldItems", unsoldItems);
35 |
36 | const shouldClearSold = soldItems >= minSoldCount;
37 |
38 | if (unsoldItems && relistUnsold) {
39 | services.Item.relistExpiredAuctions().observe(
40 | this,
41 | function (t, listResponse) {
42 | !shouldClearSold &&
43 | UTTransferListViewController.prototype.refreshList();
44 | }
45 | );
46 | }
47 |
48 | const activeTransfers = response.response.items.filter(function (item) {
49 | return item.getAuctionData().isSelling();
50 | }).length;
51 | updateStats("activeTransfers", activeTransfers);
52 |
53 | const availableItems = response.response.items.filter(function (item) {
54 | return item.getAuctionData().isInactive();
55 | }).length;
56 |
57 | updateStats("availableItems", availableItems);
58 |
59 | const userCoins = services.User.getUser().coins.amount;
60 | updateStats("coinsNumber", userCoins);
61 | updateStats("coins", userCoins.toLocaleString());
62 |
63 | if (shouldClearSold) {
64 | writeToLog(
65 | "[TRANSFER-LIST] > " + soldItems + " item(s) sold\n",
66 | idProgressAutobuyer
67 | );
68 | UTTransferListViewController.prototype._clearSold();
69 | }
70 | resolve();
71 | }
72 | );
73 | });
74 | };
75 |
--------------------------------------------------------------------------------
/app/function-overrides/NetworkIntercepts/index.js:
--------------------------------------------------------------------------------
1 | import { isMarketAlertApp } from "../../app.constants";
2 | import { getValue, setValue } from "../../services/repository";
3 |
4 | const personaUrl = "/ut/game/fifa23/usermassinfo";
5 | const squadMembersUrl = "/ut/game/fifa23/tradepile";
6 | const profileUrl = "https://gateway.ea.com/proxy/identity/pids/me";
7 |
8 | export const xmlRequestOverride = () => {
9 | let defaultRequestOpen = window.XMLHttpRequest.prototype.open;
10 |
11 | window.XMLHttpRequest.prototype.open = function (method, url, async) {
12 | this.addEventListener(
13 | "readystatechange",
14 | function () {
15 | if (this.readyState === 4) {
16 | if (isMarketAlertApp && this.responseURL.includes(personaUrl)) {
17 | let parsedResponse = JSON.parse(this.responseText);
18 | if (parsedResponse) {
19 | const { personaId, personaName } = parsedResponse.userInfo;
20 | const userEmail = getValue("useremail");
21 | window.ReactNativeWebView.postMessage(
22 | JSON.stringify({
23 | payload: {
24 | personaId,
25 | personaName,
26 | userEmail,
27 | language: services.Localization.locale.language,
28 | },
29 | type: "initUser",
30 | })
31 | );
32 | }
33 | } else if (
34 | isMarketAlertApp &&
35 | this.responseURL.includes(squadMembersUrl)
36 | ) {
37 | let parsedResponse = JSON.parse(this.responseText);
38 | if (parsedResponse) {
39 | const payload = parsedResponse.auctionInfo
40 | .filter((item) => item.itemData.assetId)
41 | .map(({ itemData }) => {
42 | const { id, assetId, definitionId, rareflag, rating } =
43 | itemData;
44 | return {
45 | id,
46 | assetId,
47 | definitionId,
48 | rareflag,
49 | rating,
50 | };
51 | });
52 | window.ReactNativeWebView.postMessage(
53 | JSON.stringify({ payload, type: "transferList" })
54 | );
55 | }
56 | } else if (this.responseURL.includes(profileUrl)) {
57 | let parsedRespose = JSON.parse(this.responseText);
58 | if (parsedRespose && parsedRespose.pid && !getValue("useremail"))
59 | setValue("useremail", parsedRespose.pid.email);
60 | }
61 | }
62 | },
63 | false
64 | );
65 | defaultRequestOpen.call(this, method, url, async);
66 | };
67 | };
68 |
--------------------------------------------------------------------------------
/app/views/layouts/Settings/SellSettingsView.js:
--------------------------------------------------------------------------------
1 | import {
2 | idAbMinDeleteCount,
3 | idAbSellPrice,
4 | idAbSellToggle,
5 | idSellAfterTax,
6 | idSellRatingThreshold,
7 | idSellFutBinPrice,
8 | idSellFutBinPercent,
9 | idSellCheckBuyPrice,
10 | idFutBinDuration,
11 | idAbDontMoveWon,
12 | idAbQuickSell,
13 | } from "../../../elementIds.constants";
14 | import { getDataSource } from "../../../services/repository";
15 | import { generateTextInput } from "../../../utils/uiUtils/generateTextInput";
16 | import { generateToggleInput } from "../../../utils/uiUtils/generateToggleInput";
17 |
18 | $(document).on("keyup", "#" + idAbSellPrice, function ({ target: { value } }) {
19 | updateAfterTax(value);
20 | });
21 |
22 | const updateAfterTax = (salePrice) => {
23 | const parsedSalePrice = parseInt(salePrice);
24 | if (isNaN(parsedSalePrice)) {
25 | $("#" + idSellAfterTax).html(0);
26 | return;
27 | }
28 | const calculatedPrice = (salePrice - (salePrice / 100) * 5).toLocaleString();
29 | $("#" + idSellAfterTax).html(calculatedPrice);
30 | };
31 |
32 | export const sellSettingsView = function () {
33 | const dataSource = getDataSource();
34 | return `
35 | ${generateToggleInput(
36 | "Find Sale Price",
37 | { idSellFutBinPrice },
38 | `(Uses ${dataSource} price for listing)`,
39 | "BuyerSettings"
40 | )}
41 | ${generateTextInput(
42 | "Sell Price Percent",
43 | "100-100",
44 | { idSellFutBinPercent },
45 | `(Sale Price percent of ${dataSource} Price)`,
46 | "BuyerSettings",
47 | "text",
48 | "\\d+-\\d+$"
49 | )}
50 | ${generateToggleInput(
51 | "Check buy price before listing",
52 | { idSellCheckBuyPrice },
53 | "(List only if Buy Price is lesser than Sale Price)",
54 | "BuyerSettings"
55 | )}
56 | ${generateToggleInput(
57 | "Quick Sell",
58 | { idAbQuickSell },
59 | "(Will quick sell card if sell price is not given)",
60 | "BuyerSettings"
61 | )}
62 | ${generateTextInput(
63 | "Sell Price",
64 | "",
65 | { idAbSellPrice },
66 | `(-1 to send to transferlist) Receive After Tax: 0 `,
67 | "BuyerSettings"
68 | )}
69 | ${generateTextInput(
70 | "List Duration",
71 | "1H",
72 | { idFutBinDuration },
73 | "List Duration when listing",
74 | "BuyerSettings",
75 | "text",
76 | "\\d+[H|M|S|h|m|s]$"
77 | )}
78 | ${generateTextInput(
79 | "Clear sold count",
80 | 10,
81 | { idAbMinDeleteCount },
82 | "(Clear sold items when reach a specified count)",
83 | "BuyerSettings"
84 | )}
85 | ${generateTextInput(
86 | "Rating Threshold",
87 | 100,
88 | { idSellRatingThreshold },
89 | "(Rating threshold to list the sniped player)",
90 | "BuyerSettings"
91 | )}
92 | ${generateToggleInput(
93 | "Relist Unsold Items",
94 | { idAbSellToggle },
95 | "",
96 | "BuyerSettings"
97 | )}
98 | ${generateToggleInput(
99 | "Dont move won items",
100 | { idAbDontMoveWon },
101 | "(Keep won items in Unassigned or Transfer Targets)",
102 | "BuyerSettings"
103 | )}
104 |
`;
105 | };
106 |
--------------------------------------------------------------------------------
/app/utils/sellUtil.js:
--------------------------------------------------------------------------------
1 | import { convertToSeconds, wait } from "./commonUtil";
2 | import { getBuyerSettings, getValue } from "../services/repository";
3 |
4 | import { getSellBidPrice } from "./priceUtils";
5 | import { idProgressAutobuyer } from "../elementIds.constants";
6 | import { sendNotificationToUser } from "./notificationUtil";
7 | import { updateProfit } from "./statsUtil";
8 | import { writeToLog } from "./logUtil";
9 |
10 | export const processSellQueue = async () => {
11 | const sellQueue = getValue("sellQueue") || [];
12 | const buyerSettings = getBuyerSettings();
13 | const sendListingNotification =
14 | buyerSettings["idAbSendListingNotificationToggle"];
15 | const hasItemInQueue = sellQueue.length;
16 | hasItemInQueue && writeToLog("--------------------", idProgressAutobuyer);
17 | while (sellQueue.length) {
18 | const { player, sellPrice, shouldList, playerName, profit } =
19 | sellQueue.pop();
20 | const message = await sellItems(
21 | player,
22 | sellPrice,
23 | profit,
24 | shouldList,
25 | buyerSettings
26 | );
27 | updateLog(
28 | sellPrice,
29 | shouldList,
30 | profit,
31 | buyerSettings,
32 | playerName,
33 | message,
34 | sendListingNotification,
35 | player.discardValue
36 | );
37 | sellQueue.length && (await wait(2));
38 | }
39 | if (hasItemInQueue) {
40 | writeToLog("--------------------", idProgressAutobuyer);
41 | }
42 | };
43 |
44 | const updateLog = (
45 | sellPrice,
46 | shouldList,
47 | profit,
48 | buyerSetting,
49 | playerName,
50 | message,
51 | sendListingNotification,
52 | discardValue
53 | ) => {
54 | const formattedMessage = `${playerName.trim()}, ${
55 | message
56 | ? message
57 | : sellPrice < 0
58 | ? "moved to transferlist"
59 | : shouldList
60 | ? "listed for: " + sellPrice + ", Profit: " + profit
61 | : buyerSetting["idAbDontMoveWon"]
62 | ? ""
63 | : buyerSetting["idAbQuickSell"]
64 | ? "quick sold for:" + discardValue
65 | : "moved to club"
66 | } `;
67 | writeToLog(formattedMessage, idProgressAutobuyer);
68 | if (sendListingNotification) {
69 | sendNotificationToUser(formattedMessage, true);
70 | }
71 | };
72 |
73 | const sellItems = (player, sellPrice, profit, shouldList, buyerSetting) => {
74 | return new Promise((resolve) => {
75 | if (sellPrice < 0) {
76 | services.Item.move(player, ItemPile.TRANSFER).observe(
77 | this,
78 | async function () {
79 | resolve();
80 | }
81 | );
82 | } else if (shouldList) {
83 | if (repositories.Item.isPileFull(ItemPile.TRANSFER)) {
84 | return resolve("Unable to list, transfer List if Full");
85 | }
86 | updateProfit(profit);
87 | services.Item.list(
88 | player,
89 | getSellBidPrice(sellPrice),
90 | sellPrice,
91 | convertToSeconds(buyerSetting["idFutBinDuration"] || "1H") || 3600
92 | ).observe(this, async function (sender, response) {
93 | resolve();
94 | });
95 | } else if (buyerSetting["idAbQuickSell"]) {
96 | services.Item.discard([player]);
97 | updateProfit(profit);
98 | resolve();
99 | } else {
100 | services.Item.move(player, ItemPile.CLUB).observe(
101 | this,
102 | async function (sender, response) {
103 | resolve();
104 | }
105 | );
106 | }
107 | });
108 | };
109 |
--------------------------------------------------------------------------------
/app/utils/filterSyncUtil.js:
--------------------------------------------------------------------------------
1 | import {
2 | idAbFiltersToUpload,
3 | idAbFiltersFileToUpload,
4 | } from "../elementIds.constants";
5 | import { getValue } from "../services/repository";
6 | import { downloadJson } from "./commonUtil";
7 | import { showPopUp } from "./popupUtil";
8 | import { saveFilterInDB } from "./userExternalUtil";
9 | import { sendUINotification } from "./notificationUtil";
10 |
11 | $(document).on(
12 | {
13 | touchend: function () {
14 | $(`#${idAbFiltersFileToUpload}`).trigger("click");
15 | },
16 | },
17 | `#${idAbFiltersFileToUpload}`
18 | );
19 |
20 | export const downloadFilters = () => {
21 | const userFilters = getValue("filters");
22 |
23 | let filterMessage = `Choose filters to Download
24 |
`;
30 | //
This is override/delete all the existings filters in the server `;
31 |
32 | showPopUp(
33 | [
34 | { labelEnum: enums.UIDialogOptions.OK },
35 | { labelEnum: enums.UIDialogOptions.CANCEL },
36 | ],
37 | "Download filters",
38 | filterMessage,
39 | async (text) => {
40 | text === 2 && (await downloadConfirm(userFilters));
41 | }
42 | );
43 | };
44 |
45 | const downloadConfirm = (userFilters) => {
46 | const filterToDownload = {};
47 | const selectedFilters = $(`#${idAbFiltersToUpload}`).val() || [];
48 | if (!selectedFilters.length) {
49 | sendUINotification("No filter selected", UINotificationType.NEGATIVE);
50 | return;
51 | }
52 | for (let filter of selectedFilters) {
53 | const currentFilter = userFilters[filter];
54 | const parsedFilter = JSON.parse(currentFilter);
55 | filterToDownload[filter] = parsedFilter;
56 | }
57 | downloadJson({ filters: filterToDownload }, "filters");
58 | sendUINotification("Filters downloaded successfully");
59 | };
60 |
61 | export const uploadFilters = () => {
62 | let uploadMessage = `Upload Filter Json file
63 |
64 |
65 | Uploading filters will override filters with the same name`;
66 | showPopUp(
67 | [
68 | { labelEnum: enums.UIDialogOptions.OK },
69 | { labelEnum: enums.UIDialogOptions.CANCEL },
70 | ],
71 | "Upload filters",
72 | uploadMessage,
73 | (text) => {
74 | text === 2 && uploadFilterConfirm();
75 | }
76 | );
77 | };
78 |
79 | const uploadFilterConfirm = () => {
80 | const myFile = $(`#${idAbFiltersFileToUpload}`).prop("files");
81 | if (!myFile || !myFile[0]) {
82 | sendUINotification("No filter file selected", UINotificationType.NEGATIVE);
83 | return;
84 | }
85 | const file = myFile[0];
86 | const fileReader = new FileReader();
87 | fileReader.onload = (evt) => {
88 | const parsedFilters = JSON.parse(evt.target.result);
89 | if (!parsedFilters || !parsedFilters.filters) {
90 | sendUINotification("Not a valid filters file");
91 | return;
92 | }
93 |
94 | for (let filter in parsedFilters.filters) {
95 | saveFilterInDB(filter, parsedFilters.filters[filter]);
96 | }
97 | sendUINotification("Filters uploaded successfully");
98 | };
99 | fileReader.readAsText(file);
100 | };
101 |
--------------------------------------------------------------------------------
/app/handlers/statsProcessor.js:
--------------------------------------------------------------------------------
1 | import {
2 | idAbCoins,
3 | idAbProfit,
4 | idAbRequestCount,
5 | idAbCountDown,
6 | idAbSearchProgress,
7 | idAbStatisticsProgress,
8 | idAbSoldItems,
9 | idAbUnsoldItems,
10 | idAbAvailableItems,
11 | idAbActiveTransfers,
12 | } from "../elementIds.constants";
13 | import { getValue, setValue } from "../services/repository";
14 | import { getTimerProgress } from "../utils/commonUtil";
15 |
16 | setValue("sessionStats", {
17 | soldItems: "-",
18 | unsoldItems: "-",
19 | activeTransfers: "-",
20 | availableItems: "-",
21 | coins: "-",
22 | coinsNumber: 0,
23 | previousPause: 0,
24 | searchCount: 0,
25 | profit: 0,
26 | transactions: [],
27 | searchPerMinuteCount: 0,
28 | });
29 |
30 | export const statsProcessor = () => {
31 | setInterval(() => {
32 | isPhone() ? phoneStatsProcessor() : webStatsProcessor();
33 | }, 1000);
34 | };
35 |
36 | const phoneStatsProcessor = () => {
37 | const nextRefresh = getTimerProgress(getValue("searchInterval"));
38 | const currentStats = getValue("sessionStats");
39 | $("#" + idAbRequestCount).html(currentStats.searchCount);
40 | $("#" + idAbProfit).html(currentStats.profit);
41 | $("#" + idAbCoins).html(currentStats.coins);
42 | $("#" + idAbSearchProgress).css("width", nextRefresh);
43 | updateTimer();
44 | };
45 |
46 | const webStatsProcessor = () => {
47 | const nextRefresh = getTimerProgress(getValue("searchInterval"));
48 | const currentStats = getValue("sessionStats");
49 | $("#" + idAbSearchProgress).css("width", nextRefresh);
50 | $("#" + idAbStatisticsProgress).css("width", nextRefresh);
51 |
52 | $("#" + idAbCoins).html(currentStats.coins);
53 | $("#" + idAbRequestCount).html(currentStats.searchCount);
54 | $("#" + idAbSoldItems).html(currentStats.soldItems);
55 | $("#" + idAbUnsoldItems).html(currentStats.unsoldItems);
56 | $("#" + idAbAvailableItems).html(currentStats.availableItems);
57 | $("#" + idAbActiveTransfers).html(currentStats.activeTransfers);
58 | $("#" + idAbProfit).html(currentStats.profit);
59 |
60 | updateTimer();
61 |
62 | if (currentStats.unsoldItems) {
63 | $("#" + idAbUnsoldItems).css("color", "red");
64 | } else {
65 | $("#" + idAbUnsoldItems).css("color", "");
66 | }
67 |
68 | if (currentStats.availableItems) {
69 | $("#" + idAbAvailableItems).css("color", "orange");
70 | } else {
71 | $("#" + idAbAvailableItems).css("color", "");
72 | }
73 | };
74 |
75 | const updateTimer = () => {
76 | const startTime = getValue("botStartTime");
77 | if (startTime && getValue("autoBuyerActive")) {
78 | const diffInSeconds = Math.abs(new Date() - startTime) / 1000;
79 | const hrs = Math.floor((diffInSeconds / 60 / 60) % 24);
80 | const mins = Math.floor((diffInSeconds / 60) % 60);
81 | const secs = Math.floor(diffInSeconds % 60);
82 | const timeString =
83 | (hrs < 10 ? "0" : "") +
84 | hrs +
85 | ":" +
86 | (mins < 10 ? "0" : "") +
87 | mins +
88 | ":" +
89 | (secs < 10 ? "0" : "") +
90 | secs;
91 | $("#" + idAbCountDown).html(timeString);
92 | updateStats("runningTime", timeString);
93 | }
94 | };
95 |
96 | export const updateStats = (key, value) => {
97 | const currentStats = getValue("sessionStats");
98 | currentStats[key] = value;
99 | setValue("sessionStats", currentStats);
100 | };
101 |
102 | export const getStatsValue = (key) => {
103 | const currentStats = getValue("sessionStats");
104 | return currentStats[key] || 0;
105 | };
106 |
--------------------------------------------------------------------------------
/app/services/datasource/futwiz.js:
--------------------------------------------------------------------------------
1 | import { sendRequest } from "../../utils/networkUtil";
2 | import { getUserPlatform } from "../../utils/userUtil";
3 | import { sendExternalRequest } from "../externalRequest";
4 | import { getValue, setValue } from "../repository";
5 |
6 | const fetchPrices = async (items) => {
7 | const result = new Map();
8 |
9 | const missingPlayers = new Map();
10 |
11 | let pendingPromises = [];
12 | for (const item of items) {
13 | if (!item.definitionId) {
14 | continue;
15 | }
16 |
17 | const priceDetail = getValue(`${item.definitionId}_futwiz_price`);
18 | if (priceDetail) {
19 | result.set(`${item.definitionId}_futwiz_price`, priceDetail.price);
20 | } else if (item.isPlayer()) {
21 | missingPlayers.set(item.definitionId, item);
22 | }
23 | }
24 |
25 | if (missingPlayers.size) {
26 | for (const item of missingPlayers.values())
27 | pendingPromises.push(getPlayerPrices(item, result));
28 | }
29 | await Promise.all(pendingPromises);
30 |
31 | return result;
32 | };
33 |
34 | const getPlayerPrices = async (player, result) => {
35 | try {
36 | const filteredPlayer = await getMatchingPlayer(player);
37 | const platform = getUserPlatform();
38 | if (!filteredPlayer) {
39 | return;
40 | }
41 | const futWizResponse = await sendRequest(
42 | `https://www.futwiz.com/en/app/sold23/${filteredPlayer[0].lineid}/console`,
43 | "GET",
44 | `${player.definitionId}_fetchFutWizPlayerPrices`
45 | );
46 | const priceResponse = JSON.parse(futWizResponse);
47 | let price = priceResponse.prices[platform].bin;
48 |
49 | if (platform === "ps" && !price) {
50 | price = priceResponse.prices["xb"].bin;
51 | }
52 |
53 | const cardPrice = parseInt(price.replace(/[,.]/g, ""));
54 |
55 | const cacheKey = `${player.definitionId}_futwiz_price`;
56 | const cacheValue = {
57 | expiryTimeStamp: new Date(Date.now() + 15 * 60 * 1000),
58 | price: cardPrice,
59 | };
60 |
61 | setValue(cacheKey, cacheValue);
62 | result.set(cacheKey, cardPrice);
63 | } catch (err) {
64 | console.log(err);
65 | }
66 | };
67 |
68 | const getMatchingPlayer = (item) => {
69 | return new Promise((resolve, reject) => {
70 | const playerName = TextUtils.stripSpecialCharacters(
71 | item._staticData.knownAs !== "---"
72 | ? item._staticData.knownAs
73 | : item._staticData.name
74 | );
75 | sendExternalRequest({
76 | url: `https://www.futwiz.com/en/searches/player23/${playerName}`,
77 | method: "GET",
78 | identifier: `${item.definitionId}_getFutWizPlayerUrl`,
79 | onload: (res) => {
80 | if (res.status !== 200) {
81 | return resolve();
82 | }
83 | const players = JSON.parse(res.response);
84 |
85 | if (!players) {
86 | return resolve();
87 | }
88 |
89 | let filteredPlayers = players.filter(
90 | (p) => parseInt(p.rating) === item.rating
91 | );
92 |
93 | if (players && !filteredPlayers.length) {
94 | filteredPlayers = players;
95 | }
96 |
97 | if (filteredPlayers && filteredPlayers.length > 1) {
98 | filteredPlayers = filteredPlayers.filter(
99 | (p) =>
100 | p.rare === item.rareflag.toString() && p.club === item.teamId + ""
101 | );
102 | }
103 | resolve(filteredPlayers);
104 | },
105 | });
106 | });
107 | };
108 |
109 | export default {
110 | fetchPrices,
111 | };
112 |
--------------------------------------------------------------------------------
/app/utils/autoActionsUtil.js:
--------------------------------------------------------------------------------
1 | import { idProgressAutobuyer } from "../elementIds.constants";
2 | import { startAutoBuyer, stopAutoBuyer } from "../handlers/autobuyerProcessor";
3 | import { updateStats } from "../handlers/statsProcessor";
4 | import {
5 | getValue,
6 | increAndGetStoreValue,
7 | setValue,
8 | } from "../services/repository";
9 | import {
10 | getRandNum,
11 | getRandNumberInRange,
12 | convertRangeToSeconds,
13 | } from "./commonUtil";
14 | import { writeToLog } from "./logUtil";
15 | import { sendNotificationToUser } from "./notificationUtil";
16 | import { loadFilter } from "./userExternalUtil";
17 | import { updateUserCredits } from "./userUtil";
18 |
19 | let stopAfter, pauseCycle;
20 |
21 | export const stopBotIfRequired = (buyerSetting) => {
22 | const purchasedCardCount = getValue("purchasedCardCount");
23 | const cardsToBuy = buyerSetting["idAbCardCount"];
24 |
25 | const botStartTime = getValue("botStartTime").getTime();
26 | let time = stopAfter || convertRangeToSeconds(buyerSetting["idAbStopAfter"]);
27 | if (!stopAfter) {
28 | stopAfter = time;
29 | }
30 | let currentTime = new Date().getTime();
31 | let timeElapsed = (currentTime - botStartTime) / 1000 >= time;
32 | const isSelling = false;
33 | // buyerSetting["idSellCheckBuyPrice"] || buyerSetting["idSellFutBinPrice"];
34 | const isTransferListFull =
35 | isSelling &&
36 | repositories.Item &&
37 | repositories.Item.transfer.length >=
38 | repositories.Item.pileSizes._collection[5];
39 |
40 | if (
41 | isTransferListFull ||
42 | timeElapsed ||
43 | (cardsToBuy && purchasedCardCount >= cardsToBuy)
44 | ) {
45 | const message = timeElapsed
46 | ? "Time elapsed"
47 | : isTransferListFull
48 | ? "Transfer list is full"
49 | : "Max purchases count reached";
50 |
51 | writeToLog(`Autobuyer stopped | ${message}`, idProgressAutobuyer);
52 | stopAfter = null;
53 | pauseCycle = null;
54 |
55 | buyerSetting["idNotificationType"] === "A" &&
56 | sendNotificationToUser(`Autobuyer Stopped - ${message}`);
57 | stopAutoBuyer();
58 | }
59 | };
60 |
61 | export const pauseBotIfRequired = function (buyerSetting) {
62 | const isBuyerActive = getValue("autoBuyerActive");
63 | if (!isBuyerActive) return;
64 | const pauseFor = convertRangeToSeconds(buyerSetting["idAbPauseFor"]) * 1000;
65 | pauseCycle =
66 | pauseCycle || getRandNumberInRange(buyerSetting["idAbCycleAmount"]);
67 |
68 | const { searchCount, previousPause } = getValue("sessionStats");
69 |
70 | if (searchCount && !((searchCount - previousPause) % pauseCycle)) {
71 | updateStats("previousPause", searchCount);
72 | updateUserCredits();
73 | stopAutoBuyer(true);
74 | return setTimeout(() => {
75 | pauseCycle = getRandNumberInRange(buyerSetting["idAbCycleAmount"]);
76 | startAutoBuyer.call(this, true);
77 | }, pauseFor);
78 | }
79 | };
80 |
81 | export const switchFilterIfRequired = async function () {
82 | const availableFilters = getValue("selectedFilters");
83 | const fiterSearchCount = getValue("fiterSearchCount");
84 | const currentFilterCount = getValue("currentFilterCount");
85 | if (
86 | !availableFilters ||
87 | !availableFilters.length ||
88 | fiterSearchCount > currentFilterCount
89 | ) {
90 | increAndGetStoreValue("currentFilterCount");
91 | return false;
92 | }
93 | setValue("currentFilterCount", 1);
94 | setValue("currentPage", 1);
95 | const currentFilterIndex = getValue("currentFilterIndex") || 0;
96 | let filterIndex = getValue("runSequentially")
97 | ? currentFilterIndex % availableFilters.length
98 | : getRandNum(0, availableFilters.length - 1);
99 |
100 | setValue("currentFilterIndex", filterIndex + 1);
101 | let filterName = availableFilters[filterIndex];
102 | await loadFilter.call(this, filterName);
103 | writeToLog(`Running for filter ${filterName}`, idProgressAutobuyer);
104 | return true;
105 | };
106 |
--------------------------------------------------------------------------------
/app/views/layouts/HeaderView.js:
--------------------------------------------------------------------------------
1 | import {
2 | idAbStatus,
3 | idAbRequestCount,
4 | idAbCoins,
5 | idAbSearchProgress,
6 | idAbProfit,
7 | idAbCountDown,
8 | idAbDownloadStats,
9 | idAbStatisticsProgress,
10 | idAbSoldItems,
11 | idAbUnsoldItems,
12 | idAbAvailableItems,
13 | idAbActiveTransfers,
14 | } from "../../elementIds.constants";
15 | import { downloadStats } from "../../utils/statsUtil";
16 | import { generateButton } from "../../utils/uiUtils/generateButton";
17 |
18 | export const BuyerStatus = () => {
19 | return `
IDLE | REQUEST COUNT:
0
20 | `;
21 | };
22 |
23 | export const HeaderView = () =>
24 | isPhone() ? HeaderViewMobile() : HeaderViewWeb();
25 |
26 | const HeaderViewMobile = () => {
27 | return `
28 |
00:00:00
30 |
Search:
31 |
34 |
Coins:
35 |
Profit:
36 | ${generateButton(
37 | idAbDownloadStats,
38 | "⇩",
39 | () => {
40 | downloadStats();
41 | },
42 | "filterSync download-stats",
43 | "Download Stats"
44 | )}
45 |
`;
46 | };
47 |
48 | const HeaderViewWeb = () => {
49 | return `
50 |
51 |
52 | ${generateButton(
53 | idAbDownloadStats,
54 | "⇩",
55 | () => {
56 | downloadStats();
57 | },
58 | "filterSync",
59 | "Download Stats"
60 | )}
61 |
62 |
63 |
64 |
65 |
66 | 00:00:00
67 |
68 |
69 |
70 |
71 |
72 |
78 |
79 |
Statistics:
80 |
83 |
84 |
85 |
86 |
87 |
Coins:
88 |
Profit:
89 |
90 |
91 |
92 | Sold Items:
93 | Unsold Items:
94 |
95 |
96 |
97 |
98 | Available Items:
99 | Active transfers:
100 |
101 |
`;
102 | };
103 |
--------------------------------------------------------------------------------
/app/handlers/autobuyerProcessor.js:
--------------------------------------------------------------------------------
1 | import { idAbStatus } from "../elementIds.constants";
2 | import { STATE_PAUSED, STATE_STOPPED } from "../app.constants";
3 | import { getBuyerSettings, getValue, setValue } from "../services/repository";
4 | import { stopBotIfRequired } from "../utils/autoActionsUtil";
5 | import { getRangeValue, playAudio } from "../utils/commonUtil";
6 | import {
7 | sendNotificationToUser,
8 | sendPinEvents,
9 | sendUINotification,
10 | } from "../utils/notificationUtil";
11 |
12 | import { setRandomInterval } from "../utils/timeOutUtil";
13 | import { addUserWatchItems } from "../utils/watchlistUtil";
14 | import {
15 | getFunctionsWithContext,
16 | setInitialValues,
17 | } from "../utils/processorUtil";
18 | import { processSellQueue } from "../utils/sellUtil";
19 |
20 | let interval = null;
21 | let passInterval = null;
22 |
23 | export const startAutoBuyer = async function (isResume) {
24 | $("#" + idAbStatus)
25 | .css("color", "#2cbe2d")
26 | .html("RUNNING");
27 |
28 | const isActive = getValue("autoBuyerActive");
29 | if (isActive) return;
30 |
31 | setInitialValues(isResume);
32 | const {
33 | switchFilterWithContext,
34 | srchTmWithContext,
35 | watchListWithContext,
36 | transferListWithContext,
37 | pauseBotWithContext,
38 | } = getFunctionsWithContext.call(this);
39 |
40 | await switchFilterWithContext();
41 | let buyerSetting = getBuyerSettings();
42 | isResume &&
43 | buyerSetting["idNotificationType"] === "A" &&
44 | sendNotificationToUser("Autobuyer Started", true);
45 | !isResume && (await addUserWatchItems());
46 | sendPinEvents("Hub - Transfers");
47 | await srchTmWithContext(buyerSetting);
48 | sendPinEvents("Hub - Transfers");
49 | await transferListWithContext(
50 | buyerSetting["idAbSellToggle"],
51 | buyerSetting["idAbMinDeleteCount"],
52 | true
53 | );
54 | let operationInProgress = false;
55 | if (getValue("autoBuyerActive")) {
56 | interval = setRandomInterval(async () => {
57 | passInterval = pauseBotWithContext(buyerSetting);
58 | stopBotIfRequired(buyerSetting);
59 | const isBuyerActive = getValue("autoBuyerActive");
60 | if (isBuyerActive && !operationInProgress) {
61 | operationInProgress = true;
62 | await processSellQueue();
63 | await switchFilterWithContext();
64 | buyerSetting = getBuyerSettings();
65 | sendPinEvents("Hub - Transfers");
66 | await srchTmWithContext(buyerSetting);
67 | sendPinEvents("Hub - Transfers");
68 | await watchListWithContext(buyerSetting);
69 | sendPinEvents("Hub - Transfers");
70 | await transferListWithContext(
71 | buyerSetting["idAbSellToggle"],
72 | buyerSetting["idAbMinDeleteCount"]
73 | );
74 | }
75 | operationInProgress = false;
76 | }, ...getRangeValue(buyerSetting["idAbWaitTime"]));
77 | }
78 | };
79 |
80 | export const stopAutoBuyer = (isPaused) => {
81 | interval && interval.clear();
82 | if (!isPaused && passInterval) {
83 | clearTimeout(passInterval);
84 | }
85 | const state = getValue("autoBuyerState");
86 | if (
87 | (isPaused && state === STATE_PAUSED) ||
88 | (!isPaused && state === STATE_STOPPED)
89 | ) {
90 | return;
91 | }
92 | setValue("autoBuyerActive", false);
93 | const searchSavedInterval = getValue("searchInterval") || {};
94 | setValue("searchInterval", {
95 | start: searchSavedInterval.start,
96 | end: Date.now(),
97 | });
98 | if (!isPaused) {
99 | playAudio("finish");
100 | }
101 | isPhone() && $(".ut-tab-bar-item").removeAttr("disabled");
102 | setValue("autoBuyerState", isPaused ? STATE_PAUSED : STATE_STOPPED);
103 | const buyerSetting = getBuyerSettings();
104 | isPaused &&
105 | buyerSetting["idNotificationType"] === "A" &&
106 | sendNotificationToUser("Autobuyer Paused", isPaused, 16705372);
107 | sendUINotification(isPaused ? "Autobuyer Paused" : "Autobuyer Stopped");
108 | if (!isPaused) {
109 | processSellQueue();
110 | }
111 | $("#" + idAbStatus)
112 | .css("color", "red")
113 | .html(isPaused ? "PAUSED" : "IDLE");
114 | };
115 |
--------------------------------------------------------------------------------
/app/views/AutoBuyerViewController.js:
--------------------------------------------------------------------------------
1 | import { idFilterDropdown, idLog } from "../elementIds.constants";
2 | import * as processors from "../handlers/autobuyerProcessor";
3 | import { statsProcessor } from "../handlers/statsProcessor";
4 | import { getValue, setValue } from "../services/repository";
5 | import { updateSettingsView } from "../utils/commonUtil";
6 | import { clearLogs } from "../utils/logUtil";
7 | import { createButton } from "./layouts/ButtonView";
8 | import { BuyerStatus, HeaderView } from "./layouts/HeaderView";
9 | import { initializeLog, logView } from "./layouts/LogView";
10 | import { clearSettingMenus, generateMenuItems } from "./layouts/MenuItemView";
11 | import { filterHeaderSettingsView } from "./layouts/Settings/FilterSettingsView";
12 |
13 | const { startAutoBuyer, stopAutoBuyer } = processors;
14 |
15 | export const AutoBuyerViewController = function (t) {
16 | UTMarketSearchFiltersViewController.call(this);
17 | };
18 |
19 | const searchFiltersViewInit =
20 | UTMarketSearchFiltersViewController.prototype.init;
21 |
22 | const searchFiltersAppear =
23 | UTMarketSearchFiltersViewController.prototype.viewDidAppear;
24 |
25 | JSUtils.inherits(AutoBuyerViewController, UTMarketSearchFiltersViewController);
26 | JSUtils.inherits(
27 | UTMarketSearchFiltersViewController,
28 | UTMarketSearchFiltersViewController
29 | );
30 |
31 | AutoBuyerViewController.prototype.init = function () {
32 | searchFiltersViewInit.call(this);
33 | let view = this.getView();
34 | if (!isPhone()) view.__root.style = "width: 52%; float: left;";
35 | setValue("AutoBuyerInstance", this);
36 |
37 | const menuItems = generateMenuItems.call(this);
38 | let root = $(view.__root);
39 | const createButtonWithContext = createButton.bind(this);
40 | const stopBtn = createButtonWithContext("Stop", () =>
41 | stopAutoBuyer.call(this)
42 | );
43 | const clearLogBtn = createButtonWithContext(
44 | "Clear Log",
45 | () => clearLogs.call(this),
46 | "btn-other"
47 | );
48 | const searchBtn = createButtonWithContext(
49 | "Start",
50 | () => {
51 | startAutoBuyer.call(this);
52 | $(`.ut-navigation-container-view--content`).animate(
53 | {
54 | scrollTop: $(`.ut-navigation-container-view--content`).prop(
55 | "scrollHeight"
56 | ),
57 | },
58 | 400
59 | );
60 | },
61 | "call-to-action"
62 | );
63 |
64 | statsProcessor();
65 |
66 | root.addClass("auto-buyer");
67 | const btnContainer = root.find(".button-container");
68 | btnContainer.addClass("buyer-actions");
69 | btnContainer.find(".call-to-action").remove();
70 | const btnReset = btnContainer.find('button:contains("Reset")');
71 | btnReset.on("click touchend", async function () {
72 | $(`#${idFilterDropdown}`).prop("selectedIndex", 0);
73 | await clearSettingMenus();
74 | });
75 | btnReset.addClass("btn-other");
76 | btnContainer.append($(searchBtn.__root));
77 | btnContainer.append($(stopBtn.__root));
78 | btnContainer.append($(clearLogBtn.__root));
79 | $(menuItems.__root).find(".menu-container").addClass("settings-menu");
80 | root.find(".search-prices").append(menuItems.__root);
81 | };
82 |
83 | AutoBuyerViewController.prototype.viewDidAppear = function () {
84 | this.getNavigationController().setNavigationVisibility(true, true);
85 | searchFiltersViewAppear.call(this, false);
86 | };
87 |
88 | UTMarketSearchFiltersViewController.prototype.viewDidAppear = function () {
89 | searchFiltersViewAppear.call(this, true);
90 | };
91 |
92 | const searchFiltersViewAppear = function (isTransferSearch) {
93 | searchFiltersAppear.call(this);
94 | let view = this.getView();
95 | let root = $(view.__root);
96 | if (!root.find(".filter-place").length) {
97 | filterHeaderSettingsView.call(this, isTransferSearch).then((res) => {
98 | root.find(".ut-item-search-view").first().prepend(res);
99 | });
100 | }
101 | };
102 |
103 | AutoBuyerViewController.prototype.getNavigationTitle = function () {
104 | setTimeout(() => {
105 | const title = $(".title");
106 | isPhone() && title.addClass("buyer-header");
107 | $(".view-navbar-currency").remove();
108 | $(".view-navbar-clubinfo").remove();
109 | title.append(BuyerStatus());
110 | $(HeaderView()).insertAfter(title);
111 | $(".ut-navigation-container-view--content").find(`#${idLog}`).remove();
112 | $(".ut-navigation-container-view--content").append(logView());
113 | initializeLog();
114 | updateSettingsView(getValue("CommonSettings") || {});
115 | });
116 | return `AutoBuyer `;
117 | };
118 |
--------------------------------------------------------------------------------
/app/views/layouts/Settings/NotificationSettingsView.js:
--------------------------------------------------------------------------------
1 | import {
2 | idAbCustomDiscordNameNotificationToggle,
3 | idAbMessageNotificationToggle,
4 | idAbSendListingNotificationToggle,
5 | idAbSoundToggle,
6 | idCapatchaMp3,
7 | idDiscordChannelId,
8 | idDiscordToken,
9 | idFUTMarketAlertToken,
10 | idFinishMp3,
11 | idNotificationType,
12 | idTelegramBotToken,
13 | idTelegramChatId,
14 | idTestNotification,
15 | idWebHookUrl,
16 | idWinMp3,
17 | } from "../../../elementIds.constants";
18 |
19 | import { generateButton } from "../../../utils/uiUtils/generateButton";
20 | import { generateTextInput } from "../../../utils/uiUtils/generateTextInput";
21 | import { generateToggleInput } from "../../../utils/uiUtils/generateToggleInput";
22 | import { sendNotificationToUser } from "../../../utils/notificationUtil";
23 | import { isMarketAlertApp } from "../../../app.constants";
24 |
25 | export const notificationSettingsView = function () {
26 | return `
27 | ${
28 | !isMarketAlertApp
29 | ? `${generateTextInput(
30 | "Telegram Bot Token",
31 | "",
32 | { idTelegramBotToken },
33 | "Token of your own bot",
34 | "CommonSettings",
35 | "text"
36 | )}
37 | ${generateTextInput(
38 | "Telegram Chat ID",
39 | "",
40 | { idTelegramChatId },
41 | "Your Telegram ChatID",
42 | "CommonSettings",
43 | "text"
44 | )}
45 | ${generateTextInput(
46 | "Discord Bot Token",
47 | "",
48 | { idDiscordToken },
49 | "Your Discord Bot Token",
50 | "CommonSettings",
51 | "text"
52 | )}
53 | ${generateTextInput(
54 | "Discord Channel ID",
55 | "",
56 | { idDiscordChannelId },
57 | "Your Discord Channel ID",
58 | "CommonSettings",
59 | "text"
60 | )}
61 | ${generateTextInput(
62 | "Discord WebHook Url",
63 | "",
64 | { idWebHookUrl },
65 | "Your Discord Channel Webhook Url",
66 | "CommonSettings",
67 | "text"
68 | )}
69 | ${generateTextInput(
70 | "Fut Market Alert Notification Token",
71 | "",
72 | { idFUTMarketAlertToken },
73 | "Your FUT Market Alert App's Token",
74 | "CommonSettings",
75 | "text"
76 | )}
77 | ${generateToggleInput(
78 | "Send Listing Notification",
79 | { idAbSendListingNotificationToggle },
80 | "",
81 | "CommonSettings"
82 | )}`
83 | : ""
84 | }
85 | ${generateTextInput(
86 | "Notification Type",
87 | "",
88 | { idNotificationType },
89 | "Type A for all notifications, B for buy or L for lost",
90 | "CommonSettings",
91 | "text"
92 | )}
93 | ${generateToggleInput(
94 | "Send Notification",
95 | { idAbMessageNotificationToggle },
96 | "",
97 | "CommonSettings"
98 | )}
99 | ${
100 | !isMarketAlertApp
101 | ? `${generateToggleInput(
102 | "Sound Notification",
103 | { idAbSoundToggle },
104 | "",
105 | "CommonSettings"
106 | )}
107 |
108 |
109 |
110 | "Your browser does not support the audio element"
111 |
112 |
113 |
114 |
115 | "Your browser does not support the audio element"
116 |
117 |
118 |
119 |
120 | "Your browser does not support the audio element"
121 | `
122 | : ""
123 | }
124 | ${generateToggleInput(
125 | "Use Custom Discord Webhook Name",
126 | { idAbCustomDiscordNameNotificationToggle },
127 | "",
128 | "CommonSettings"
129 | )}
130 |
131 | ${generateButton(
132 | idTestNotification,
133 | "Test Notification",
134 | () =>
135 | sendNotificationToUser(
136 | "Test Notification Message",
137 | true,
138 | undefined,
139 | true
140 | ),
141 | "call-to-action"
142 | )}
143 |
144 | `;
145 | };
146 |
--------------------------------------------------------------------------------
/app/handlers/captchaSolver.js:
--------------------------------------------------------------------------------
1 | import { idProgressAutobuyer } from "../elementIds.constants";
2 | import { getBuyerSettings, getValue } from "../services/repository";
3 | import { showCaptchaLogs, writeToLog } from "../utils/logUtil";
4 | import { startAutoBuyer } from "./autobuyerProcessor";
5 |
6 | export const solveCaptcha = () => {
7 | const buyerSetting = getBuyerSettings();
8 | let apikey = buyerSetting["idAntiCaptchKey"];
9 | let websiteURL = "https://www.ea.com/fifa/ultimate-team/web-app/";
10 | let websitePublicKey = "A4EECF77-AC87-8C8D-5754-BF882F72063B";
11 |
12 | let proxyAddress = buyerSetting["idProxyAddress"];
13 | let proxyPort = buyerSetting["idProxyPort"];
14 | let proxyLogin = buyerSetting["idProxyLogin"];
15 | let proxyPassword = buyerSetting["idProxyPassword"];
16 |
17 | if (!proxyAddress || !proxyPort || !apikey) {
18 | writeToLog("Proxy info not filled properly", idProgressAutobuyer);
19 | showCaptchaLogs(buyerSetting["idAbCloseTabToggle"]);
20 | return;
21 | }
22 |
23 | function createTask() {
24 | accessobjects.Captcha.getCaptchaData().observe(
25 | this,
26 | function (sender, responseData) {
27 | if (responseData.success) {
28 | var data = responseData.response.blob;
29 | if (!data) {
30 | return false;
31 | }
32 |
33 | let payload = {
34 | clientKey: apikey,
35 | task: {
36 | type: "FunCaptchaTask",
37 | websiteURL: websiteURL,
38 | websitePublicKey: websitePublicKey,
39 | funcaptchaApiJSSubdomain: "ea-api.arkoselabs.com",
40 | data: responseData.response,
41 | proxyType: "http",
42 | proxyAddress: proxyAddress,
43 | proxyPort: proxyPort,
44 | proxyLogin: proxyLogin,
45 | proxyPassword: proxyPassword,
46 | userAgent:
47 | "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36",
48 | },
49 | };
50 |
51 | var xhr = new XMLHttpRequest();
52 | xhr.open("POST", "https://api.anti-captcha.com/createTask", true);
53 | xhr.setRequestHeader("Content-Type", "application/json");
54 | xhr.onreadystatechange = function () {
55 | if (xhr.readyState === 4 && xhr.status === 200) {
56 | var json = JSON.parse(xhr.responseText);
57 | if (json.errorId == 0) {
58 | getTaskResult(json.taskId);
59 | } else {
60 | writeToLog(
61 | "Got error from Captcha API: " +
62 | json.errorCode +
63 | ", " +
64 | json.errorDescription,
65 | idProgressAutobuyer
66 | );
67 | }
68 | }
69 | };
70 | var data = JSON.stringify(payload);
71 | xhr.send(data);
72 | return true;
73 | }
74 | }
75 | );
76 | }
77 |
78 | function getTaskResult(taskId) {
79 | let payload = {
80 | clientKey: apikey,
81 | taskId: taskId,
82 | };
83 | var xhr = new XMLHttpRequest();
84 | xhr.open("POST", "https://api.anti-captcha.com/getTaskResult", true);
85 | xhr.setRequestHeader("Content-Type", "application/json");
86 | xhr.onreadystatechange = function () {
87 | if (xhr.readyState === 4 && xhr.status === 200) {
88 | var json = JSON.parse(xhr.responseText);
89 | if (json.errorId == 0) {
90 | //no errors
91 | if (json.status == "ready") {
92 | let captchaModel = new UTCaptchaViewModel(accessobjects.Captcha);
93 | captchaModel
94 | .validateToken(json.solution.token)
95 | .observe(this, function (sender, response) {
96 | if (response.success) {
97 | writeToLog("Captcha Solved", idProgressAutobuyer);
98 | const instance = getValue("AutoBuyerInstance");
99 | startAutoBuyer.call(instance);
100 | }
101 | });
102 | } else {
103 | setTimeout(() => getTaskResult(taskId), 1000);
104 | }
105 | } else {
106 | writeToLog(
107 | "Error occured when checking captcha result : " +
108 | json.errorCode +
109 | ", " +
110 | json.errorDescription,
111 | idProgressAutobuyer
112 | );
113 | }
114 | }
115 | };
116 | var data = JSON.stringify(payload);
117 | xhr.send(data);
118 | }
119 |
120 | createTask();
121 | };
122 |
--------------------------------------------------------------------------------
/app/services/datasource/futbin.js:
--------------------------------------------------------------------------------
1 | import { getCardName } from "../../utils/futItemUtil";
2 | import { sendRequest } from "../../utils/networkUtil";
3 | import { getUserPlatform } from "../../utils/userUtil";
4 | import { getValue, setValue } from "../repository";
5 |
6 | const supportedConsumables = new Set(["Position", "Chemistry Style"]);
7 |
8 | const fetchPrices = async (items) => {
9 | const result = new Map();
10 |
11 | const missingPlayerIds = new Set();
12 | const missingConsumables = new Map();
13 |
14 | for (const item of items) {
15 | if (!item.definitionId) {
16 | continue;
17 | }
18 |
19 | const priceDetail = getValue(`${item.definitionId}_futbin_price`);
20 | if (priceDetail) {
21 | result.set(`${item.definitionId}_futbin_price`, priceDetail.price);
22 | } else if (item.isPlayer()) {
23 | missingPlayerIds.add(item.definitionId);
24 | } else if (
25 | item.isTraining() &&
26 | supportedConsumables.has(item._staticData.name)
27 | ) {
28 | if (!missingConsumables.has(item._staticData.name)) {
29 | missingConsumables.set(item._staticData.name, []);
30 | }
31 | missingConsumables.get(item._staticData.name).push({
32 | definitionId: item.definitionId,
33 | subType: getCardName(item),
34 | });
35 | }
36 | }
37 |
38 | const pendingPromises = [];
39 |
40 | if (missingPlayerIds.size) {
41 | pendingPromises.push(fetchPlayerPrices(missingPlayerIds, result));
42 | }
43 |
44 | if (missingConsumables.size) {
45 | pendingPromises.push(fetchConsumablesPrices(missingConsumables, result));
46 | }
47 | await Promise.all(pendingPromises);
48 |
49 | return result;
50 | };
51 |
52 | const fetchPlayerPrices = async (playerIds, result) => {
53 | const idsArray = Array.from(playerIds);
54 | const platform = getUserPlatform();
55 | while (idsArray.length) {
56 | const playersIdArray = idsArray.splice(0, 30);
57 | const primaryId = playersIdArray.shift();
58 | if (!primaryId) {
59 | continue;
60 | }
61 | const refIds = playersIdArray.join(",");
62 | try {
63 | const futBinResponse = await sendRequest(
64 | `https://www.futbin.com/23/playerPrices?player=${primaryId}&rids=${refIds}`,
65 | "GET",
66 | `${Math.floor(+new Date())}_fetchPlayerPrices`
67 | );
68 |
69 | const priceResponse = JSON.parse(futBinResponse);
70 |
71 | for (const id of [primaryId, ...playersIdArray]) {
72 | const prices = priceResponse[id].prices[platform];
73 | const lcPrice = prices.LCPrice;
74 | if (!lcPrice) {
75 | continue;
76 | }
77 | const cardPrice = parseInt(lcPrice.replace(/[,.]/g, ""));
78 |
79 | const cacheKey = `${id}_futbin_price`;
80 | const cacheValue = {
81 | expiryTimeStamp: new Date(Date.now() + 15 * 60 * 1000),
82 | price: cardPrice,
83 | };
84 |
85 | setValue(cacheKey, cacheValue);
86 | result.set(cacheKey, cardPrice);
87 | }
88 | } catch (err) {
89 | console.log(err);
90 | }
91 | }
92 | };
93 |
94 | const fetchConsumablesPrices = async (missingConsumables, result) => {
95 | const platform = getUserPlatform();
96 | const futBinPlatform =
97 | platform === "ps" ? "PS" : platform === "xbox" ? "XB" : "PC";
98 | const consumableTypes = Array.from(missingConsumables.keys());
99 | for (const consumableType of consumableTypes) {
100 | try {
101 | const category = consumableType.split(" ")[0];
102 | const futBinResponse = await sendRequest(
103 | `https://www.futbin.org/futbin/api/fetchConsumables?category=${category}&platformtype=${futBinPlatform}`,
104 | "GET",
105 | `${Math.floor(+new Date())}_fetchConsumablesPrices`
106 | );
107 |
108 | const priceResponse = JSON.parse(futBinResponse);
109 | const consumablesPriceLookUp = priceResponse.data.reduce((acc, curr) => {
110 | acc.set(curr.SubType.toUpperCase(), curr.LCPrice);
111 | return acc;
112 | }, new Map());
113 | const consumableCards = missingConsumables.get(consumableType) || [];
114 | for (const { definitionId, subType } of consumableCards) {
115 | let cardPrice = consumablesPriceLookUp.get(subType);
116 | const cacheKey = `${definitionId}_futbin_price`;
117 | if (cardPrice) {
118 | const cacheValue = {
119 | expiryTimeStamp: new Date(Date.now() + 15 * 60 * 1000),
120 | price: cardPrice,
121 | };
122 | setValue(cacheKey, cacheValue);
123 | result.set(cacheKey, cardPrice);
124 | }
125 | }
126 | } catch (err) {
127 | console.log(err);
128 | }
129 | }
130 | };
131 |
132 | export default {
133 | fetchPrices,
134 | };
135 |
--------------------------------------------------------------------------------
/app/views/layouts/MenuItemView.js:
--------------------------------------------------------------------------------
1 | import { buySettingsView } from "./Settings/BuySettingsView";
2 | import { sellSettingsView } from "./Settings/SellSettingsView";
3 | import { safeSettingsView } from "./Settings/SafeSettingsView";
4 | import { captchaSettingsView } from "./Settings/CaptchaSettingsView";
5 | import { notificationSettingsView } from "./Settings/NotificationSettingsView";
6 | import { commonSettingsView } from "./Settings/CommonSettingsView";
7 | import {
8 | destoryPlayerInput,
9 | searchSettingsView,
10 | } from "./Settings/SearchSettingsView";
11 | import { getValue, setValue } from "../../services/repository";
12 | import { filterSettingsView } from "./Settings/FilterSettingsView";
13 | import { getUserFilters } from "../../utils/dbUtil";
14 | import { idAbSortBy } from "../../elementIds.constants";
15 |
16 | const settingsLookup = new Map();
17 | settingsLookup.set(0, {
18 | label: "Buy/Bid Settings",
19 | selector: ".buy-settings-view",
20 | });
21 | settingsLookup.set(1, {
22 | label: "Sell Settings",
23 | selector: ".sell-settings-view",
24 | });
25 | settingsLookup.set(2, {
26 | label: "Search Settings",
27 | selector: ".results-filter-view",
28 | });
29 | settingsLookup.set(3, {
30 | label: "Safety Settings",
31 | selector: ".safety-settings-view",
32 | });
33 | settingsLookup.set(4, {
34 | label: "Filter Settings",
35 | selector: ".filter-settings-view",
36 | });
37 | settingsLookup.set(5, {
38 | label: "Captcha Settings",
39 | selector: ".captcha-settings-view",
40 | });
41 | settingsLookup.set(6, {
42 | label: "Notification Settings",
43 | selector: ".notification-settings-view",
44 | });
45 | settingsLookup.set(7, {
46 | label: "Common Settings",
47 | selector: ".common-settings-view",
48 | });
49 |
50 | let menuRoot;
51 | let menuItems;
52 |
53 | export const generateMenuItems = function () {
54 | menuItems = new EAFilterBarView();
55 | settingsLookup.forEach((value, key) => {
56 | menuItems.addTab(key, value.label);
57 | });
58 | menuItems.setActiveTab(0);
59 | menuItems.layoutSubviews();
60 |
61 | menuItems.addTarget(this, onSettingChange, EventType.TAP);
62 | menuItems.__root.style = "margin-top: 20px;";
63 |
64 | menuRoot = $(menuItems.__root);
65 | menuRoot.find(".menu-container").css("overflow-x", "auto");
66 |
67 | appendMenuItems(true);
68 | return menuItems;
69 | };
70 |
71 | export const setDefaultActiveTab = () => {
72 | menuItems.setActiveTab(0);
73 | $(".menu-container").animate({
74 | scrollLeft: 0,
75 | });
76 | };
77 |
78 | export const clearSettingMenus = async function () {
79 | deleteAllMenu();
80 | clearSettingsCache();
81 | await appendMenuItems();
82 | const autoBuyerInstance = getValue("AutoBuyerInstance");
83 | UTMarketSearchFiltersViewController.prototype._eResetSelected.call(
84 | autoBuyerInstance
85 | );
86 | };
87 |
88 | export const updateCommonSettings = async (isInit) => {
89 | let commonSettings = await getUserFilters("CommonSettings");
90 | commonSettings = JSON.parse(commonSettings["CommonSettings"] || "{}");
91 | if (!$.isEmptyObject(commonSettings)) {
92 | const currentValue = isInit ? getValue("CommonSettings") : {};
93 | setValue("CommonSettings", Object.assign({}, currentValue, commonSettings));
94 | }
95 | };
96 |
97 | const appendMenuItems = async function (isInit) {
98 | menuItems.setActiveTab(0);
99 | menuRoot.append(buySettingsView.call(this));
100 | menuRoot.append(sellSettingsView.call(this));
101 | menuRoot.append(searchSettingsView.call(this));
102 | menuRoot.append(safeSettingsView.call(this));
103 | const filterVal = await filterSettingsView.call(this);
104 | menuRoot.append(filterVal);
105 | menuRoot.append(captchaSettingsView.call(this));
106 | menuRoot.append(notificationSettingsView.call(this));
107 | menuRoot.append(commonSettingsView.call(this));
108 |
109 | $(".menu-container").animate({
110 | scrollLeft: 0,
111 | });
112 |
113 | setTimeout(async () => {
114 | const selectedFilters = getValue("selectedFilters") || [];
115 | const { idAbSortBy: sortBy } = getValue("BuyerSettings") || {};
116 | if (sortBy) {
117 | $(`${idAbSortBy} option[value='${sortBy}']`).prop("selected", "selected");
118 | }
119 | $.each(selectedFilters, function (idx, val) {
120 | $(".multiselect-filter option[value='" + val + "']").prop(
121 | "selected",
122 | "selected"
123 | );
124 | });
125 | if (isInit) {
126 | await updateCommonSettings();
127 | }
128 | });
129 | };
130 |
131 | const deleteAllMenu = () => {
132 | settingsLookup.forEach((value, key) => {
133 | $(value.selector).remove();
134 | });
135 | destoryPlayerInput();
136 | };
137 |
138 | const onSettingChange = function (e, t, i) {
139 | hideAllSection();
140 | const selectedTab = settingsLookup.get(i.index).selector;
141 | $(selectedTab).css("display", "");
142 | };
143 |
144 | const hideAllSection = () => {
145 | settingsLookup.forEach((value, key) => {
146 | $(value.selector).css("display", "none");
147 | });
148 | };
149 |
150 | const clearSettingsCache = () => {
151 | setValue("currentFilter", null);
152 | setValue("BuyerSettings", {});
153 | setValue("currentFilter", {});
154 | };
155 |
--------------------------------------------------------------------------------
/app/utils/userExternalUtil.js:
--------------------------------------------------------------------------------
1 | import { defaultBuyerSetting, defaultCommonSetting } from "../app.constants";
2 | import * as ElementIds from "../elementIds.constants";
3 | import { getValue, getBuyerSettings, setValue } from "../services/repository";
4 | import {
5 | clearSettingMenus,
6 | updateCommonSettings,
7 | } from "../views/layouts/MenuItemView";
8 | import { updateSettingsView } from "./commonUtil";
9 | import { deleteFilters, insertFilters } from "./dbUtil";
10 | import { checkAndAppendOption, updateMultiFilterSettings } from "./filterUtil";
11 | import { sendUINotification } from "./notificationUtil";
12 |
13 | const validateSettings = () => {
14 | if (document.querySelectorAll(":invalid").length) {
15 | sendUINotification(
16 | "Settings with invalid value found, fix these values for autobuyer to work as intended",
17 | UINotificationType.NEGATIVE
18 | );
19 | }
20 | };
21 |
22 | const filterDropdownId = `#${ElementIds.idFilterDropdown}`;
23 | const selectedFilterId = `#${ElementIds.idSelectedFilter}`;
24 |
25 | export const saveFilterDetails = function (self) {
26 | const btnContext = this;
27 | $(btnContext).addClass("active");
28 | let buyerSetting = getBuyerSettings(true);
29 | let commonSettings = getValue("CommonSettings");
30 | setTimeout(function () {
31 | let settingsJson = {};
32 | const viewModel = self._viewmodel;
33 | settingsJson.searchCriteria = {
34 | criteria: viewModel.searchCriteria,
35 | playerData: viewModel.playerData,
36 | buyerSettings: buyerSetting,
37 | };
38 |
39 | let currentFilterName = $(`${filterDropdownId} option`)
40 | .filter(":selected")
41 | .val();
42 |
43 | if (currentFilterName === "Choose filter to load") {
44 | currentFilterName = undefined;
45 | }
46 | let filterName = prompt("Enter a name for this filter", currentFilterName);
47 | validateSettings();
48 | if (filterName) {
49 | if (filterName.toLocaleUpperCase() === "_DEFAULT") {
50 | return sendUINotification(
51 | "Cannot override _DEFAULT filter",
52 | UINotificationType.NEGATIVE
53 | );
54 | }
55 | saveFilterInDB(filterName, settingsJson);
56 | insertFilters(
57 | "CommonSettings",
58 | JSON.stringify(commonSettings),
59 | "CommonSettings"
60 | );
61 | setValue("currentFilter", filterName);
62 | $(btnContext).removeClass("active");
63 | sendUINotification("Changes saved successfully");
64 | } else {
65 | $(btnContext).removeClass("active");
66 | sendUINotification("Filter Name Required", UINotificationType.NEGATIVE);
67 | }
68 | }, 200);
69 | };
70 |
71 | export const saveFilterInDB = (filterName, settingsJson) => {
72 | filterName = filterName.toUpperCase();
73 | checkAndAppendOption(filterDropdownId, filterName);
74 | checkAndAppendOption(`#${ElementIds.idSelectedFilter}`, filterName);
75 | $(`${filterDropdownId} option[value="${filterName}"]`).attr("selected", true);
76 | getValue("filters")[filterName] = JSON.stringify(settingsJson);
77 | insertFilters(filterName, getValue("filters")[filterName]);
78 | };
79 |
80 | const loadDefaultFilter = () => {
81 | setValue("BuyerSettings", Object.assign({}, defaultBuyerSetting));
82 | setValue("CommonSettings", Object.assign({}, defaultCommonSetting));
83 | const buyerSettings = getBuyerSettings();
84 | updateSettingsView(buyerSettings);
85 | };
86 |
87 | export const loadFilter = async function (currentFilterName, isTransferSearch) {
88 | if (currentFilterName === "_default") {
89 | loadDefaultFilter();
90 | return false;
91 | }
92 | if (!getValue("runnerToggle") && !isTransferSearch) await clearSettingMenus();
93 | const filterSetting = getValue("filters")[currentFilterName];
94 | if (!filterSetting) return false;
95 | let {
96 | searchCriteria: { criteria, playerData, buyerSettings },
97 | } = JSON.parse(filterSetting);
98 |
99 | this._viewmodel.resetSearch();
100 | this.viewDidAppear();
101 |
102 | this._viewmodel.playerData = {};
103 | Object.assign(this._viewmodel.searchCriteria, criteria);
104 | Object.assign(this._viewmodel.playerData, playerData);
105 |
106 | if ($.isEmptyObject(this._viewmodel.playerData)) {
107 | this._viewmodel.playerData = null;
108 | }
109 |
110 | this.viewDidAppear();
111 |
112 | if (isTransferSearch) {
113 | return;
114 | }
115 |
116 | await updateCommonSettings();
117 | const commonSettings = getValue("CommonSettings") || {};
118 | setValue("BuyerSettings", buyerSettings);
119 | setValue("currentFilter", currentFilterName);
120 | buyerSettings = Object.assign({}, buyerSettings, commonSettings);
121 |
122 | updateSettingsView(buyerSettings);
123 |
124 | if (
125 | buyerSettings["idAddIgnorePlayersList"] &&
126 | buyerSettings["idAddIgnorePlayersList"].length
127 | ) {
128 | for (let { displayName } of buyerSettings["idAddIgnorePlayersList"]) {
129 | checkAndAppendOption(
130 | `#${ElementIds.idAddIgnorePlayersList}`,
131 | displayName
132 | );
133 | }
134 | }
135 | validateSettings();
136 |
137 | return true;
138 | };
139 |
140 | export const deleteFilter = async function () {
141 | const filterName = $(`${filterDropdownId} option`).filter(":selected").val();
142 | if (filterName.toLocaleUpperCase() === "_DEFAULT") {
143 | return sendUINotification(
144 | "Cannot delete _DEFAULT filter",
145 | UINotificationType.NEGATIVE
146 | );
147 | }
148 | if (filterName != "Choose filter to load") {
149 | $(`${filterDropdownId}` + ` option[value="${filterName}"]`).remove();
150 | $(`${filterDropdownId}`).prop("selectedIndex", 0);
151 |
152 | await clearSettingMenus();
153 | this.viewDidAppear();
154 |
155 | delete getValue("filters")[filterName];
156 | $(`${selectedFilterId}` + ` option[value="${filterName}"]`).remove();
157 | updateMultiFilterSettings();
158 | deleteFilters(filterName);
159 | sendUINotification("Changes saved successfully");
160 | }
161 | };
162 |
--------------------------------------------------------------------------------
/app/function-overrides/css-override.js:
--------------------------------------------------------------------------------
1 | export const cssOverride = () => {
2 | const style = document.createElement("style");
3 | $(".ui-orientation-warning").css("display", "none");
4 | $(".ut-fifa-header-view").css("display", "none");
5 | style.innerText = `
6 | .buyer-header {
7 | font-size: 20px !important;
8 | }
9 | .with-fifa-header .ut-root-view {
10 | height: 100%;
11 | }
12 | .buyer-settings {
13 | width: 100%;
14 | }
15 | .buyer-settings-field {
16 | margin-top: 15px;
17 | margin-bottom: 15px;
18 | }
19 | .phone .buyer-settings-field{
20 | margin-top: auto;
21 | margin-bottom: auto;
22 | width: 100%;
23 | padding: 10px;
24 | }
25 | .buyer-settings-wrapper {
26 | display: flex;
27 | flex-wrap: wrap;
28 | margin-top: 20px;
29 | }
30 | .buyer-settings-field .ut-toggle-cell-view{
31 | justify-content: center;
32 | }
33 | .buyer-settings-field input:disabled {
34 | background-color: #c3c6ce;
35 | cursor: not-allowed;
36 | }
37 | .btn-test-notification
38 | {
39 | width: 100%;
40 | display: flex;
41 | justify-content: center;
42 | align-items: center;
43 | }
44 | input[type="number"]{
45 | padding: 0 .5em;
46 | border-radius: 0;
47 | background-color: #262c38;
48 | border: 1px solid #4ee6eb;
49 | box-sizing: border-box;
50 | color: #4ee6eb;
51 | font-family: UltimateTeam,sans-serif;
52 | font-size: 1em;
53 | height: 2.8em;
54 | opacity: 1;
55 | width: 100%;
56 | }
57 | .autoBuyerLog {
58 | font-size: ${!isPhone() ? "15px" : "13px"};
59 | height: 50%;
60 | background-color:#141414;
61 | color:#e2dde2;
62 | }
63 | .searchLog {
64 | font-size: 10px;
65 | height: 50%;
66 | }
67 | input::-webkit-outer-spin-button,
68 | input::-webkit-inner-spin-button {
69 | -webkit-appearance: none;
70 | margin: 0;
71 | }
72 | input[type=number] {
73 | -moz-appearance: textfield;
74 | }
75 | .captcha-settings-view input,
76 | .notification-settings-view input {
77 | text-transform: none;
78 | }
79 | .phone .buyer-header{
80 | font-size: 1.2em !important;
81 | }
82 | .phone .buyer-actions .btn-standard{
83 | padding: 0;
84 | font-size: 1.2em;
85 | text-overflow: unset;
86 | }
87 | .filter-header-settings {
88 | width: 100%;
89 | padding: 10px;
90 | font-family: UltimateTeamCondensed, sans-serif;
91 | font-size: 1.6em;
92 | color: #e2dde2;
93 | text-transform: uppercase;
94 | background-color: #171826;
95 | }
96 | .btn-save-filter {
97 | width:100%
98 | }
99 | .btn-delete-filter {
100 | width:50%
101 | }
102 | .multiple-filter {
103 | width: 100% !important;
104 | display: flex !important;
105 | justify-content: center;
106 | align-items: center;
107 | }
108 | .logs-container {
109 | display: flex;
110 | justify-content: space-between;
111 | font-size: 20px;
112 | align-items: center;
113 | }
114 | .button-clear button {
115 | color: #fff;
116 | background-color: unset;
117 | height: unset;
118 | line-height: unset;
119 | }
120 | .top-nav{
121 | display:flex;
122 | }
123 | .ut-navigation-button-control.menu-btn:before {
124 | content: "≡";
125 | transform: unset;
126 | }
127 | .menu-btn {
128 | min-width: 0px;
129 | margin-left: 5px;
130 | }
131 | .filterSync {
132 | background: transparent;
133 | color: #c4f750;
134 | text-overflow: clip;
135 | }
136 | .filterSync:hover {
137 | background: transparent !important;
138 | }
139 | .stats-progress {
140 | float: right;
141 | height: 10px;
142 | width: 100px;
143 | background: #888;
144 | margin: ${isPhone() ? "auto 5px" : "5px 0px 5px 5px"};
145 | }
146 | .stats-fill {
147 | background: #000;
148 | height: 10px;
149 | width: 0%
150 | }
151 | .numericInput:invalid {
152 | color: red;
153 | border: 1px solid;
154 | }
155 | .ignore-players{
156 | width: 100%;
157 | display: flex;
158 | background: transparent;
159 | }
160 | .ignore-players .ut-player-search-control{
161 | width: 90% !important;
162 | }
163 | .ignore-players filterSync{
164 | flex: unset;
165 | }
166 | .font15 {
167 | font-size: 15px;
168 | }
169 | .action-icons {
170 | display: unset !important;
171 | width: 10%
172 | }
173 | .displayCenterFlx {
174 | display: flex;
175 | align-items: center;
176 | justify-content: center;
177 | }
178 | .displayNone {
179 | height: 275px;
180 | }
181 | .displayNone .inline-list-select,
182 | .displayNone .search-prices,
183 | .displayNone .btn-actions,
184 | .displayNone .btn-filters,
185 | .displayNone .btn-report,
186 | .displayNone .buyer-actions .btn-other {
187 | display: none !important;
188 | }
189 | .mrg10 {
190 | margin: 10px;
191 | }
192 | .ut-toggle-cell-view--label{
193 | overflow: unset;
194 | }
195 | .download-stats {
196 | line-height: 1;
197 | display: flex;
198 | }
199 | .btn-report {
200 | display: flex;
201 | justify-content: center;
202 | }
203 | small{
204 | white-space: break-spaces;
205 | }
206 | .joinServer {
207 | position: absolute;
208 | right: 25px;
209 | top: 50%;
210 | color: wheat
211 | }
212 | .phone .joinServer{
213 | display: none;
214 | }
215 | textarea {
216 | resize: none;
217 | }
218 | .logWrapper {
219 | position: relative;
220 | height: 100%
221 | }
222 |
223 | .auto-buyer .autoBuyMin{
224 | display: none;
225 | }
226 | .auto-buyer .search-prices .settings-field{
227 | display: none;
228 | }
229 | `;
230 | style.innerText += getScrollBarStyle();
231 | document.head.appendChild(style);
232 | };
233 |
234 | const getScrollBarStyle = () => {
235 | return `
236 | ::-webkit-scrollbar {
237 | -webkit-appearance: none;
238 | }
239 | ::-webkit-scrollbar:vertical {
240 | width: 12px;
241 | }
242 | ::-webkit-scrollbar:horizontal {
243 | height: 12px;
244 | }
245 | ::-webkit-scrollbar-thumb {
246 | background-color: rgba(0, 0, 0, .5);
247 | border-radius: 10px;
248 | border: 2px solid #ffffff;
249 | }
250 | ::-webkit-scrollbar-track {
251 | border-radius: 10px;
252 | background-color: #ffffff;
253 | }`;
254 | };
255 |
--------------------------------------------------------------------------------
/app/elementIds.constants.js:
--------------------------------------------------------------------------------
1 | import { generateId } from "./utils/commonUtil";
2 |
3 | export const idFilterDropdown = "elem_" + generateId(15);
4 | export const idFilterDropdownHeader = "elem_" + generateId(15);
5 | export const idSelectedFilter = "elem_" + generateId(15);
6 | export const idAbBuyPrice = "elem_" + generateId(15);
7 | export const idAbCardCount = "elem_" + generateId(15);
8 | export const idAbMaxBid = "elem_" + generateId(15);
9 | export const idAbSearchResult = "elem_" + generateId(15);
10 | export const idAbItemExpiring = "elem_" + generateId(15);
11 | export const idAbBidExact = "elem_" + generateId(15);
12 | export const idAbSellPrice = "elem_" + generateId(15);
13 | export const idAbMinDeleteCount = "elem_" + generateId(15);
14 | export const idAbSellToggle = "elem_" + generateId(15);
15 | export const idAbWaitTime = "elem_" + generateId(15);
16 | export const idAbCycleAmount = "elem_" + generateId(15);
17 | export const idAbPauseFor = "elem_" + generateId(15);
18 | export const idAbStopAfter = "elem_" + generateId(15);
19 | export const idAbMinRating = "elem_" + generateId(15);
20 | export const idAbMaxRating = "elem_" + generateId(15);
21 | export const idAbRandMinBidInput = "elem_" + generateId(15);
22 | export const idAbRandMinBuyInput = "elem_" + generateId(15);
23 | export const idAbRandMinBuyToggle = "elem_" + generateId(15);
24 | export const idAbRandMinBidToggle = "elem_" + generateId(15);
25 | export const idAbMaxSearchPage = "elem_" + generateId(15);
26 | export const idAbAddBuyDelay = "elem_" + generateId(15);
27 | export const idAbDelayToAdd = "elem_" + generateId(15);
28 | export const idAbAddFilterGK = "elem_" + generateId(15);
29 | export const idAbCloseTabToggle = "elem_" + generateId(15);
30 | export const idAbMessageNotificationToggle = "elem_" + generateId(15);
31 | export const idAbCustomDiscordNameNotificationToggle = "elem_" + generateId(15);
32 | export const idAbSendListingNotificationToggle = "elem_" + generateId(15);
33 | export const idAbSoundToggle = "elem_" + generateId(15);
34 | export const idTelegramBotToken = "elem_" + generateId(15);
35 | export const idTelegramChatId = "elem_" + generateId(15);
36 | export const idNotificationType = "elem_" + generateId(15);
37 | export const idDiscordToken = "elem_" + generateId(15);
38 | export const idDiscordChannelId = "elem_" + generateId(15);
39 | export const idFUTMarketAlertToken = "elem_" + generateId(15);
40 | export const idProgressAutobuyer = "elem_" + generateId(15);
41 | export const idSearchCancelButton = "elem_" + generateId(15);
42 | export const idInfoWrapper = "elem_" + generateId(15);
43 | export const idPreserveChanges = "elem_" + generateId(15);
44 | export const idClearLogButton = "elem_" + generateId(15);
45 | export const idCalcBinPrice = "elem_" + generateId(15);
46 | export const idTestNotification = "elem_" + generateId(15);
47 | export const idSelectFilterCount = "elem_" + generateId(15);
48 | export const idDeleteFilter = "elem_" + generateId(15);
49 | export const idAbSolveCaptcha = "elem_" + generateId(15);
50 | export const idSellAfterTax = "elem_" + generateId(15);
51 | export const idAbSearchProgress = "elem_" + generateId(15);
52 | export const idAbStatisticsProgress = "elem_" + generateId(15);
53 | export const idAbRequestCount = "elem_" + generateId(15);
54 | export const idAbCoins = "elem_" + generateId(15);
55 | export const idAbStatus = "elem_" + generateId(15);
56 | export const idAbSoldItems = "elem_" + generateId(15);
57 | export const idAbUnsoldItems = "elem_" + generateId(15);
58 | export const idAbAvailableItems = "elem_" + generateId(15);
59 | export const idAbActiveTransfers = "elem_" + generateId(15);
60 | export const idAbNumberFilterSearch = "elem_" + generateId(15);
61 | export const idAbStopErrorCode = "elem_" + generateId(15);
62 | export const idAbStopErrorCodeCount = "elem_" + generateId(15);
63 | export const idSearchWrapper = "elem_" + generateId(15);
64 | export const idWinMp3 = "elem_" + generateId(15);
65 | export const idCapatchaMp3 = "elem_" + generateId(15);
66 | export const idProxyAddress = "elem_" + generateId(15);
67 | export const idProxyPort = "elem_" + generateId(15);
68 | export const idProxyLogin = "elem_" + generateId(15);
69 | export const idAntiCaptchKey = "elem_" + generateId(15);
70 | export const idProxyPassword = "elem_" + generateId(15);
71 | export const idAutoClearLog = "elem_" + generateId(15);
72 | export const idSellRatingThreshold = "elem_" + generateId(15);
73 | export const idSellFutBinPrice = "elem_" + generateId(15);
74 | export const idSellFutBinPercent = "elem_" + generateId(15);
75 | export const idAbUploadFilter = "elem_" + generateId(15);
76 | export const idAbDownloadFilter = "elem_" + generateId(15);
77 | export const idAbFiltersToUpload = "elem_" + generateId(15);
78 | export const idAbProfit = "elem_" + generateId(15);
79 | export const idSellCheckBuyPrice = "elem_" + generateId(15);
80 | export const idRunFilterSequential = "elem_" + generateId(15);
81 | export const idAddIgnorePlayers = "elem_" + generateId(15);
82 | export const idRemoveIgnorePlayers = "elem_" + generateId(15);
83 | export const idAddIgnorePlayersList = "elem_" + generateId(15);
84 | export const idFutBinDuration = "elem_" + generateId(15);
85 | export const idAbFiltersFileToUpload = "elem_" + generateId(15);
86 | export const idAbUploadBtn = "elem_" + generateId(15);
87 | export const idAutoClearExpired = "elem_" + generateId(15);
88 | export const idAbIgnoreAllowToggle = "elem_" + generateId(15);
89 | export const idBuyFutBinPrice = "elem_" + generateId(15);
90 | export const idBuyFutBinPercent = "elem_" + generateId(15);
91 | export const idAbToggleRunner = "elem_" + generateId(15);
92 | export const idAbBidFutBin = "elem_" + generateId(15);
93 | export const idAbDontMoveWon = "elem_" + generateId(15);
94 | export const idAbResumeAfterErrorOccured = "elem_" + generateId(15);
95 | export const idAbReportProblem = "elem_" + generateId(15);
96 | export const idAbCountDown = "elem_" + generateId(15);
97 | export const idAbDownloadStats = "elem_" + generateId(15);
98 | export const idAbSortBy = "elem_" + generateId(15);
99 | export const idAbSortOrder = "elem_" + generateId(15);
100 | export const idAbShouldSort = "elem_" + generateId(15);
101 | export const idBtnActions = "elem_" + generateId(15);
102 | export const idBtnReport = "elem_" + generateId(15);
103 | export const idTooltip = "elem_" + generateId(15);
104 | export const idSession = generateId(15);
105 | export const idWebHookUrl = "elem_" + generateId(15);
106 | export const idFinishMp3 = "elem_" + generateId(15);
107 | export const idLog = "elem_" + generateId(15);
108 | export const idAbOverSearchWarning = "elem_" + generateId(15);
109 | export const idAbMaxPurchases = "elem_" + generateId(15);
110 | export const idAbUseFutWiz = "elem_" + generateId(15);
111 | export const idAbQuickSell = "elem_" + generateId(15);
112 | export const idAbAddFilterBC = "elem_" + generateId(15);
113 |
--------------------------------------------------------------------------------
/app/utils/commonUtil.js:
--------------------------------------------------------------------------------
1 | import { isMarketAlertApp } from "../app.constants";
2 | import * as ElementIds from "../elementIds.constants";
3 | import { getBuyerSettings } from "../services/repository";
4 |
5 | export const generateId = (length) => {
6 | let result = "";
7 | let characters =
8 | "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
9 | let charactersLength = characters.length;
10 | for (var i = 0; i < length; i++) {
11 | result += characters.charAt(Math.floor(Math.random() * charactersLength));
12 | }
13 | return result;
14 | };
15 |
16 | export const wait = async (seconds = 1) => {
17 | const rndFactor = Math.floor(Math.random());
18 | await new Promise((resolve) =>
19 | setTimeout(resolve, (rndFactor + seconds) * 1000)
20 | );
21 | };
22 |
23 | export const showLoader = () => {
24 | $(".ut-click-shield").addClass("showing");
25 | $(".loaderIcon ").css("display", "block");
26 | };
27 |
28 | export const hideLoader = () => {
29 | $(".ut-click-shield").removeClass("showing");
30 | $(".loaderIcon ").css("display", "none");
31 | };
32 |
33 | export const downloadJson = (json, fileName) => {
34 | isMarketAlertApp
35 | ? downloadJsonPhone(json, fileName)
36 | : downloadJsonWeb(json, fileName);
37 | };
38 |
39 | export const downloadCsv = (csvContent, fileName) => {
40 | isMarketAlertApp
41 | ? downloadCsvPhone(csvContent, fileName)
42 | : downloadCsvWeb(csvContent, fileName);
43 | };
44 |
45 | const downloadJsonPhone = (json, fileName) => {
46 | window.ReactNativeWebView.postMessage(
47 | JSON.stringify({
48 | type: "downloadFile",
49 | payload: { data: JSON.stringify(json, null, 4), fileName },
50 | })
51 | );
52 | };
53 |
54 | const downloadCsvPhone = (csvContent, fileName) => {
55 | window.ReactNativeWebView.postMessage(
56 | JSON.stringify({
57 | type: "downloadFile",
58 | payload: { data: csvContent, fileName },
59 | })
60 | );
61 | };
62 |
63 | const downloadJsonWeb = (json, fileName) => {
64 | const dataStr =
65 | "data:text/json;charset=utf-8," +
66 | encodeURIComponent(JSON.stringify(json, null, 4));
67 | const link = document.createElement("a");
68 | link.setAttribute("href", dataStr);
69 | link.setAttribute("download", `${fileName}.json`);
70 | document.body.appendChild(link);
71 | link.click();
72 | document.body.removeChild(link);
73 | };
74 |
75 | const downloadCsvWeb = (csvContent, fileName) => {
76 | const encodedUri =
77 | "data:text/csv;charset=utf-8,%EF%BB%BF" + encodeURIComponent(csvContent);
78 | const link = document.createElement("a");
79 | link.setAttribute("href", encodedUri);
80 | link.setAttribute("download", `${fileName}.csv`);
81 | document.body.appendChild(link);
82 | link.click();
83 | document.body.removeChild(link);
84 | };
85 |
86 | export const convertRangeToSeconds = (val) => {
87 | if (val) {
88 | val = val + "";
89 | let valInterval = val[val.length - 1].toUpperCase();
90 | let valInTime = getRandWaitTime(val.substring(0, val.length - 1)) / 1000;
91 | let multipler = valInterval === "M" ? 60 : valInterval === "H" ? 3600 : 1;
92 | if (valInTime) {
93 | valInTime = valInTime * multipler;
94 | }
95 | return valInTime;
96 | }
97 | return 0;
98 | };
99 | export const getRandNumberInRange = (range) => {
100 | const rangeVal = getRangeValue(range);
101 | if (rangeVal.length >= 2) {
102 | return getRandNum(rangeVal[0], rangeVal[1]);
103 | }
104 | return rangeVal[0] || 0;
105 | };
106 |
107 | export const getRandWaitTime = (range) => {
108 | if (range) {
109 | const [start, end] = range.split("-").map((a) => parseInt(a));
110 | return Math.round(Math.random() * (end - start) + start) * 1000;
111 | }
112 | return 0;
113 | };
114 |
115 | export const convertToSeconds = (val) => {
116 | if (val) {
117 | let valInterval = val[val.length - 1].toUpperCase();
118 | let valInTime = parseInt(val.substring(0, val.length - 1));
119 | let multipler = valInterval === "M" ? 60 : valInterval === "H" ? 3600 : 1;
120 | if (valInTime) {
121 | valInTime = valInTime * multipler;
122 | }
123 | return valInTime;
124 | }
125 | return 0;
126 | };
127 |
128 | export const getRandNum = (min, max) =>
129 | Math.round(Math.random() * (max - min) + min);
130 |
131 | export const getRangeValue = (range) => {
132 | if (range) {
133 | return (range + "").split("-").map((a) => parseInt(a));
134 | }
135 | return [];
136 | };
137 |
138 | export const formatString = (str, len) => {
139 | if (str.length <= len) {
140 | str += " ".repeat(len - str.length);
141 | }
142 | return str;
143 | };
144 |
145 | export const promisifyTimeOut = (cb, wait) => {
146 | return new Promise((resolve) => {
147 | setTimeout(function () {
148 | cb();
149 | resolve();
150 | }, 1000);
151 | });
152 | };
153 |
154 | export const playAudio = function (eventType) {
155 | const buyerSetting = getBuyerSettings();
156 | if (!isMarketAlertApp && buyerSetting["idAbSoundToggle"]) {
157 | let elem = document.getElementById(ElementIds.idWinMp3);
158 |
159 | if (eventType == "capatcha") {
160 | elem = document.getElementById(ElementIds.idCapatchaMp3);
161 | } else if (eventType == "finish") {
162 | elem = document.getElementById(ElementIds.idFinishMp3);
163 | } else if (eventType == "cardWon") {
164 | elem = document.getElementById(ElementIds.idWinMp3);
165 | }
166 |
167 | elem.currentTime = 0;
168 | elem.play();
169 | }
170 | };
171 |
172 | export const networkCallWithRetry = (execution, delay, retries) =>
173 | new Promise((resolve, reject) => {
174 | return execution()
175 | .then(resolve)
176 | .catch((reason) => {
177 | if (retries > 0) {
178 | return wait(delay)
179 | .then(
180 | networkCallWithRetry.bind(null, execution, delay, retries - 1)
181 | )
182 | .then(resolve)
183 | .catch(reject);
184 | }
185 | return reject(reason);
186 | });
187 | });
188 |
189 | export const createElementFromHTML = (htmlString) => {
190 | var div = document.createElement("div");
191 | div.innerHTML = htmlString.trim();
192 | return div.firstChild;
193 | };
194 |
195 | export const getTimerProgress = function (timer) {
196 | if (!timer) return 0;
197 | var time = new Date().getTime();
198 | return (Math.max(0, timer.end - time) / (timer.end - timer.start)) * 100;
199 | };
200 |
201 | export const updateSettingsView = function (settings) {
202 | for (let key of Object.keys(settings)) {
203 | const value = settings[key];
204 | if (settings[key + "isDefaultValue"]) continue;
205 | const id = `#${ElementIds[key]}`;
206 | if (typeof value == "boolean") {
207 | if (value) {
208 | $(id).addClass("toggled");
209 | continue;
210 | }
211 | $(id).removeClass("toggled");
212 | } else {
213 | $(id).val(value);
214 | }
215 | }
216 | };
217 |
--------------------------------------------------------------------------------
/app/utils/purchaseUtil.js:
--------------------------------------------------------------------------------
1 | import {
2 | convertRangeToSeconds,
3 | convertToSeconds,
4 | formatString,
5 | playAudio,
6 | wait,
7 | } from "./commonUtil";
8 | import {
9 | getBuyerSettings,
10 | getValue,
11 | increAndGetStoreValue,
12 | setValue,
13 | } from "../services/repository";
14 | import { startAutoBuyer, stopAutoBuyer } from "../handlers/autobuyerProcessor";
15 |
16 | import { appendTransactions } from "./statsUtil";
17 | import { errorCodeLookUp } from "../app.constants";
18 | import { getSellPriceFromFutBin } from "./futbinUtil";
19 | import { idProgressAutobuyer } from "../elementIds.constants";
20 | import { sendNotificationToUser } from "./notificationUtil";
21 | import { writeToLog } from "./logUtil";
22 |
23 | const errorCodeCountMap = new Map();
24 |
25 | export const buyPlayer = (
26 | player,
27 | playerName,
28 | price,
29 | sellPrice,
30 | isBin,
31 | tradeId
32 | ) => {
33 | const buyerSetting = getBuyerSettings();
34 | return new Promise((resolve) => {
35 | services.Item.bid(player, price).observe(
36 | this,
37 | async function (sender, data) {
38 | let priceTxt = formatString(price.toString(), 6);
39 | const notificationType = buyerSetting["idNotificationType"] || "";
40 |
41 | if (data.success) {
42 | if (isBin) {
43 | increAndGetStoreValue("purchasedCardCount");
44 | playAudio("cardWon");
45 | }
46 | const ratingThreshold = buyerSetting["idSellRatingThreshold"];
47 | let playerRating = parseInt(player.rating);
48 | const isValidRating =
49 | !ratingThreshold || playerRating <= ratingThreshold;
50 |
51 | const useFutBinPrice = buyerSetting["idSellFutBinPrice"];
52 | if (isValidRating && useFutBinPrice && isBin) {
53 | sellPrice = await getSellPriceFromFutBin(
54 | buyerSetting,
55 | playerName,
56 | player
57 | );
58 | }
59 |
60 | const checkBuyPrice = buyerSetting["idSellCheckBuyPrice"];
61 | if (checkBuyPrice && price > (sellPrice * 95) / 100) {
62 | sellPrice = -1;
63 | }
64 |
65 | const shouldList = sellPrice && !isNaN(sellPrice) && isValidRating;
66 | const profit =
67 | (buyerSetting["idAbQuickSell"]
68 | ? player.discardValue
69 | : sellPrice * 0.95) - price;
70 |
71 | if (isBin) {
72 | const winCount = increAndGetStoreValue("winCount");
73 | appendTransactions(
74 | `[${new Date().toLocaleTimeString()}] ${playerName.trim()} buy success - Price : ${price}`
75 | );
76 | writeToLog(
77 | `W: ${winCount} ${playerName} buy success added to sell queue`,
78 | idProgressAutobuyer
79 | );
80 |
81 | if (!buyerSetting["idAbDontMoveWon"]) {
82 | const sellQueue = getValue("sellQueue") || [];
83 | sellQueue.push({
84 | player,
85 | playerName,
86 | sellPrice,
87 | shouldList,
88 | profit,
89 | });
90 | setValue("sellQueue", sellQueue);
91 | }
92 | } else {
93 | const bidCount = increAndGetStoreValue("bidCount");
94 | appendTransactions(
95 | `[${new Date().toLocaleTimeString()}] ${playerName.trim()} bid success - Price : ${price}`
96 | );
97 | writeToLog(
98 | `B:${bidCount} ${playerName} bid success`,
99 | idProgressAutobuyer
100 | );
101 | const filterName = getValue("currentFilter") || "default";
102 | if (filterName) {
103 | const bidItemsByFilter = getValue("filterBidItems") || new Map();
104 | if (bidItemsByFilter.has(filterName)) {
105 | bidItemsByFilter.get(filterName).add(tradeId);
106 | } else {
107 | bidItemsByFilter.set(filterName, new Set([tradeId]));
108 | }
109 | setValue("filterBidItems", bidItemsByFilter);
110 | }
111 | }
112 |
113 | if (notificationType.includes("B") || notificationType === "A") {
114 | sendNotificationToUser(
115 | "| " +
116 | playerName.trim() +
117 | " | " +
118 | priceTxt.trim() +
119 | ` | ${isBin ? "buy" : "bid"} |`,
120 | true
121 | );
122 | }
123 | } else {
124 | let lossCount = increAndGetStoreValue("lossCount");
125 | appendTransactions(
126 | `[${new Date().toLocaleTimeString()}] ${playerName.trim()} buy failed - Price : ${price}`
127 | );
128 | let status = ((data.error && data.error.code) || data.status) + "";
129 | writeToLog(
130 | `L: ${lossCount} ${playerName} ${
131 | isBin ? "buy" : "bid"
132 | } failure ERR: (${
133 | errorCodeLookUp[status] + "(" + status + ")" || status
134 | })`,
135 | idProgressAutobuyer
136 | );
137 | if (notificationType.includes("L") || notificationType === "A") {
138 | sendNotificationToUser(
139 | "| " +
140 | playerName.trim() +
141 | " | " +
142 | priceTxt.trim() +
143 | " | failure |",
144 | false
145 | );
146 | }
147 |
148 | if (buyerSetting["idAbStopErrorCode"]) {
149 | const errorCodes = new Set(
150 | buyerSetting["idAbStopErrorCode"].split(",")
151 | );
152 |
153 | if (!errorCodeCountMap.has(status))
154 | errorCodeCountMap.set(status, { currentVal: 0 });
155 |
156 | errorCodeCountMap.get(status).currentVal++;
157 |
158 | if (
159 | errorCodes.has(status) &&
160 | errorCodeCountMap.get(status).currentVal >=
161 | buyerSetting["idAbStopErrorCodeCount"]
162 | ) {
163 | writeToLog(
164 | `[!!!] Autostopping bot since error code ${status} has occured ${
165 | errorCodeCountMap.get(status).currentVal
166 | } times\n`,
167 | idProgressAutobuyer
168 | );
169 | errorCodeCountMap.clear();
170 | stopAutoBuyer();
171 |
172 | if (buyerSetting["idAbResumeAfterErrorOccured"]) {
173 | const pauseFor = convertRangeToSeconds(
174 | buyerSetting["idAbResumeAfterErrorOccured"]
175 | );
176 |
177 | writeToLog(
178 | `Bot will resume after ${pauseFor}(s)`,
179 | idProgressAutobuyer
180 | );
181 | setTimeout(() => {
182 | startAutoBuyer.call(getValue("AutoBuyerInstance"));
183 | }, pauseFor * 1000);
184 | }
185 | }
186 | }
187 | }
188 | buyerSetting["idAbAddBuyDelay"] &&
189 | (await wait(convertToSeconds(buyerSetting["idAbDelayToAdd"])));
190 | resolve();
191 | }
192 | );
193 | });
194 | };
195 |
--------------------------------------------------------------------------------
/app/utils/notificationUtil.js:
--------------------------------------------------------------------------------
1 | import { getBuyerSettings, getValue, setValue } from "../services/repository";
2 | import { startAutoBuyer, stopAutoBuyer } from "../handlers/autobuyerProcessor";
3 |
4 | import { loadFilter } from "./userExternalUtil";
5 | import { isMarketAlertApp } from "../app.constants";
6 |
7 | let discordClient = null;
8 |
9 | export const sendUINotification = function (message, notificationType) {
10 | notificationType = notificationType || UINotificationType.POSITIVE;
11 | services.Notification.queue([message, notificationType]);
12 | };
13 |
14 | export const sendPinEvents = (pageId) => {
15 | services.PIN.sendData(PINEventType.PAGE_VIEW, {
16 | type: PIN_PAGEVIEW_EVT_TYPE,
17 | pgid: pageId,
18 | });
19 | };
20 |
21 | export const sendNotificationToUser = (
22 | message,
23 | isSuccess,
24 | color,
25 | isTestMessage
26 | ) => {
27 | const buyerSetting = getBuyerSettings();
28 | if (buyerSetting["idAbMessageNotificationToggle"] || isTestMessage) {
29 | isMarketAlertApp
30 | ? sendNotificationToExternalPhone(message)
31 | : sendNotificationToExternal(buyerSetting, isSuccess, message, color);
32 | isTestMessage && sendUINotification("Test Notification Sent");
33 | }
34 | };
35 |
36 | const sendNotificationToExternalPhone = (message) => {
37 | window.ReactNativeWebView.postMessage(
38 | JSON.stringify({ type: "Notification", message })
39 | );
40 | };
41 |
42 | const formDiscordMessage = (message, isSuccess, isCustomDiscordName, color) => {
43 | let embedMessage = {
44 | embeds: [
45 | {
46 | description: message,
47 | color: color ? color : isSuccess ? 2555648 : 16711680,
48 | footer: {
49 | text: `Auto Buyer Alert - ${new Date().toLocaleTimeString()} - Balance: ${services.User.getUser().coins.amount}`,
50 | icon_url:
51 | "https://cdn.discordapp.com/icons/768336764447621122/9de9ea0a7c6239e2f2fbfbd716189e79.webp",
52 | },
53 | },
54 | ],
55 | avatar_url:
56 | "https://cdn.discordapp.com/icons/768336764447621122/9de9ea0a7c6239e2f2fbfbd716189e79.webp",
57 | username: "Fut Market Alert",
58 | };
59 | isCustomDiscordName ? delete embedMessage["username"] : null;
60 | return embedMessage;
61 | };
62 |
63 | const formDiscordRichEmbed = (discordMessage) => {
64 | const {
65 | embeds: [message],
66 | } = discordMessage;
67 | return new Discord.RichEmbed()
68 | .setColor(message.color)
69 | .setDescription(message.description)
70 | .setFooter(message.footer.text, message.footer.icon_url);
71 | };
72 |
73 | const sendNotificationToExternal = (
74 | buyerSetting,
75 | isSuccess,
76 | message,
77 | color
78 | ) => {
79 | const telegramToken = buyerSetting["idTelegramBotToken"];
80 | const telegramChatId = buyerSetting["idTelegramChatId"];
81 | const webHookUrl = buyerSetting["idWebHookUrl"];
82 | const channelId = buyerSetting["idDiscordChannelId"];
83 | const alertAppNotificationToken = buyerSetting["idFUTMarketAlertToken"];
84 | const isCustomDiscordName =
85 | buyerSetting["idAbCustomDiscordNameNotificationToggle"];
86 | sendMessageToTelegram(telegramToken, telegramChatId, message);
87 | sendMessageToDiscord(
88 | channelId,
89 | isSuccess,
90 | message,
91 | isCustomDiscordName,
92 | color
93 | );
94 | sendMessageToDiscordWH(
95 | webHookUrl,
96 | isSuccess,
97 | message,
98 | isCustomDiscordName,
99 | color
100 | );
101 | sendMessageToAlertApp(alertAppNotificationToken, message);
102 | };
103 |
104 | const sendMessageToTelegram = (telegramToken, telegramChatId, message) => {
105 | if (telegramToken && telegramChatId) {
106 | const url = `https://api.telegram.org/bot${telegramToken}/sendMessage?chat_id=${telegramChatId}&parse_mode=Markdown&text=${message}`;
107 | const xhttp = new XMLHttpRequest();
108 | xhttp.open("GET", url, true);
109 | xhttp.send();
110 | }
111 | };
112 |
113 | const sendMessageToDiscordWH = (
114 | webHookUrl,
115 | isSuccess,
116 | message,
117 | isCustomDiscordName,
118 | color
119 | ) => {
120 | if (webHookUrl) {
121 | fetch(webHookUrl, {
122 | method: "POST",
123 | headers: { "Content-Type": "application/json" },
124 | body: JSON.stringify(
125 | formDiscordMessage(message, isSuccess, isCustomDiscordName, color)
126 | ),
127 | });
128 | }
129 | };
130 |
131 | const sendMessageToDiscord = (
132 | channelId,
133 | isSuccess,
134 | message,
135 | isCustomDiscordName,
136 | color
137 | ) => {
138 | if (channelId) {
139 | if (discordClient) {
140 | const channel = discordClient.channels.get(channelId);
141 | channel &&
142 | channel.send(
143 | formDiscordRichEmbed(
144 | formDiscordMessage(message, isSuccess, isCustomDiscordName, color)
145 | )
146 | );
147 | } else {
148 | discordClient = initializeDiscordClient(() => {
149 | setTimeout(() => {
150 | if (discordClient) {
151 | const channel = discordClient.channels.get(channelId);
152 | channel &&
153 | channel.send(
154 | formDiscordRichEmbed(
155 | formDiscordMessage(
156 | message,
157 | isSuccess,
158 | isCustomDiscordName,
159 | color
160 | )
161 | )
162 | );
163 | }
164 | }, 200);
165 | });
166 | }
167 | }
168 | };
169 |
170 | const initializeDiscordClient = (cb) => {
171 | const buyerSetting = getBuyerSettings();
172 | const client = new Discord.Client();
173 | let discordToken = buyerSetting["idDiscordToken"];
174 | if (!discordToken) return null;
175 | try {
176 | client.login(discordToken);
177 | client.on("ready", function () {
178 | if (cb) {
179 | cb();
180 | }
181 | });
182 | client.on("message", async function (message) {
183 | if (message.author.id == client.user.id) return;
184 | if (/start/i.test(message.content)) {
185 | const instance = getValue("AutoBuyerInstance");
186 | startAutoBuyer.call(instance);
187 | message.channel.send(
188 | formDiscordRichEmbed(
189 | formDiscordMessage("Bot started successfully", true)
190 | )
191 | );
192 | } else if (/stop/i.test(message.content)) {
193 | stopAutoBuyer();
194 | message.channel.send(
195 | formDiscordRichEmbed(
196 | formDiscordMessage("Bot stopped successfully", true)
197 | )
198 | );
199 | } else if (/runfilter/i.test(message.content)) {
200 | let filterName = message.content.split("-")[1];
201 | if (filterName) {
202 | filterName = filterName.toUpperCase();
203 | stopAutoBuyer();
204 | setValue("selectedFilters", []);
205 | const instance = getValue("AutoBuyerInstance");
206 | const isSuccess = await loadFilter.call(instance, filterName);
207 | if (!isSuccess) {
208 | message.channel.send(
209 | formDiscordRichEmbed(
210 | formDiscordMessage(`unable to find filter${filterName}`, false)
211 | )
212 | );
213 | return;
214 | }
215 | startAutoBuyer.call(instance);
216 | message.channel.send(
217 | formDiscordRichEmbed(
218 | formDiscordMessage(`${filterName} started successfully`, true)
219 | )
220 | );
221 | } else {
222 | message.channel.send(
223 | formDiscordRichEmbed(
224 | formDiscordMessage("Unable to find filter name", false)
225 | )
226 | );
227 | }
228 | }
229 | });
230 | } catch (err) {}
231 | return client;
232 | };
233 |
234 | const sendMessageToAlertApp = (notificationToken, message) => {
235 | if (notificationToken) {
236 | fetch("https://exp.host/--/api/v2/push/send", {
237 | method: "POST",
238 | headers: { "Content-Type": "application/json" },
239 | body: JSON.stringify([
240 | {
241 | to: `ExponentPushToken[${notificationToken}]`,
242 | title: message,
243 | body: message,
244 | },
245 | ]),
246 | });
247 | }
248 | };
249 |
--------------------------------------------------------------------------------
/app/utils/watchlistUtil.js:
--------------------------------------------------------------------------------
1 | import { idProgressAutobuyer } from "../elementIds.constants";
2 | import { getValue, setValue } from "../services/repository";
3 | import { formatString, getRandNum, wait } from "./commonUtil";
4 | import { getSellPriceFromFutBin } from "./futbinUtil";
5 | import { writeToLog } from "./logUtil";
6 | import { sendPinEvents } from "./notificationUtil";
7 | import { getBuyBidPrice, getSellBidPrice } from "./priceUtils";
8 | import { buyPlayer } from "./purchaseUtil";
9 |
10 | const sellBids = new Set();
11 |
12 | export const watchListUtil = function (buyerSetting) {
13 | sendPinEvents("Transfer Targets - List View");
14 |
15 | return new Promise((resolve) => {
16 | services.Item.clearTransferMarketCache();
17 |
18 | services.Item.requestWatchedItems().observe(this, function (t, response) {
19 | let bidPrice = buyerSetting["idAbMaxBid"];
20 | let sellPrice = buyerSetting["idAbSellPrice"];
21 |
22 | let activeItems = response.response.items.filter(function (item) {
23 | return !!item._auction;
24 | });
25 |
26 | if (!activeItems.length) {
27 | return resolve();
28 | }
29 |
30 | services.Item.refreshAuctions(activeItems).observe(
31 | this,
32 | function (t, refreshResponse) {
33 | services.Item.requestWatchedItems().observe(
34 | this,
35 | async function (t, watchResponse) {
36 | const isAutoBuyerActive = getValue("autoBuyerActive");
37 | const filterName = getValue("currentFilter") || "default";
38 | const bidItemsByFilter = getValue("filterBidItems") || new Map();
39 | const filterWatchList =
40 | bidItemsByFilter.get(filterName) || new Set();
41 |
42 | const userWatchItems = getValue("userWatchItems");
43 |
44 | if (isAutoBuyerActive && bidPrice) {
45 | let outBidItems = watchResponse.response.items.filter(function (
46 | item
47 | ) {
48 | return (
49 | item._auction._bidState === "outbid" &&
50 | (!filterName ||
51 | filterWatchList.has(item._auction.tradeId)) &&
52 | !userWatchItems.has(item._auction.tradeId) &&
53 | item._auction._tradeState === "active"
54 | );
55 | });
56 |
57 | if (outBidItems.length) {
58 | const currentItem =
59 | outBidItems[getRandNum(0, outBidItems.length - 1)];
60 | await tryBidItems(
61 | currentItem,
62 | bidPrice,
63 | sellPrice,
64 | buyerSetting
65 | );
66 | }
67 | }
68 |
69 | const useFutBinPrice = buyerSetting["idSellFutBinPrice"];
70 |
71 | if (
72 | isAutoBuyerActive &&
73 | !buyerSetting["idAbDontMoveWon"] &&
74 | ((sellPrice && !isNaN(sellPrice)) || useFutBinPrice)
75 | ) {
76 | let boughtItems = watchResponse.response.items.filter(function (
77 | item
78 | ) {
79 | return (
80 | item.getAuctionData().isWon() &&
81 | (!filterName ||
82 | filterWatchList.has(item._auction.tradeId)) &&
83 | !userWatchItems.has(item._auction.tradeId) &&
84 | !sellBids.has(item._auction.tradeId)
85 | );
86 | });
87 |
88 | for (var i = 0; i < boughtItems.length; i++) {
89 | const player = boughtItems[i];
90 | const ratingThreshold = buyerSetting["idSellRatingThreshold"];
91 | let playerRating = parseInt(player.rating);
92 | const isValidRating =
93 | !ratingThreshold || playerRating <= ratingThreshold;
94 | let playerName = formatString(player._staticData.name, 15);
95 | let price = player._auction.currentBid;
96 |
97 | if (isValidRating && useFutBinPrice) {
98 | sellPrice = await getSellPriceFromFutBin(
99 | buyerSetting,
100 | playerName,
101 | player
102 | );
103 | }
104 |
105 | const checkBuyPrice = buyerSetting["idSellCheckBuyPrice"];
106 | if (checkBuyPrice && price > (sellPrice * 95) / 100) {
107 | sellPrice = -1;
108 | }
109 |
110 | const shouldList =
111 | sellPrice && !isNaN(sellPrice) && isValidRating;
112 |
113 | if (shouldList) {
114 | sellBids.add(player._auction.tradeId);
115 | }
116 | if (!buyerSetting["idAbDontMoveWon"]) {
117 | const sellQueue = getValue("sellQueue") || [];
118 | const profit =
119 | (buyerSetting["idAbQuickSell"]
120 | ? player.discardValue
121 | : sellPrice * 0.95) - price;
122 | sellQueue.push({
123 | player,
124 | sellPrice,
125 | playerName,
126 | shouldList,
127 | profit,
128 | });
129 | setValue("sellQueue", sellQueue);
130 | }
131 | }
132 | }
133 |
134 | let expiredItems = watchResponse.response.items.filter((item) => {
135 | var t = item.getAuctionData();
136 | return t.isExpired() || (t.isClosedTrade() && !t.isWon());
137 | });
138 |
139 | if (buyerSetting["idAutoClearExpired"] && expiredItems.length) {
140 | services.Item.untarget(expiredItems);
141 | writeToLog(
142 | `Found ${expiredItems.length} expired items and removed from watchlist`,
143 | idProgressAutobuyer
144 | );
145 | }
146 |
147 | services.Item.clearTransferMarketCache();
148 | resolve();
149 | }
150 | );
151 | }
152 | );
153 | });
154 | });
155 | };
156 |
157 | export const addUserWatchItems = () => {
158 | return new Promise((resolve, reject) => {
159 | services.Item.requestWatchedItems().observe(this, function (t, response) {
160 | if (response.success) {
161 | const bidItemsByFilter = getValue("filterBidItems") || new Map();
162 | const botBiddedTrades = new Set(
163 | Array.from(bidItemsByFilter.values())
164 | .flat(1)
165 | .reduce((a, c) => a.concat([...c]), [])
166 | );
167 | const userWatchItems =
168 | response.response.items
169 | .filter(
170 | (item) =>
171 | item._auction && !botBiddedTrades.has(item._auction.tradeId)
172 | )
173 | .map((item) => item._auction.tradeId) || [];
174 |
175 | setValue("userWatchItems", new Set(userWatchItems));
176 |
177 | if (userWatchItems.length) {
178 | writeToLog(
179 | `Found ${userWatchItems.length} items in users watch list and ignored from selling`,
180 | idProgressAutobuyer
181 | );
182 | }
183 | }
184 | resolve();
185 | });
186 | });
187 | };
188 |
189 | const tryBidItems = async (player, bidPrice, sellPrice, buyerSetting) => {
190 | let auction = player._auction;
191 | let isBid = auction.currentBid;
192 | let currentBid = auction.currentBid || auction.startingBid;
193 | let playerName = formatString(player._staticData.name, 15);
194 | const isAutoBuyerActive = getValue("autoBuyerActive");
195 |
196 | let priceToBid = buyerSetting["idAbBidExact"]
197 | ? bidPrice
198 | : isBid
199 | ? getSellBidPrice(bidPrice)
200 | : bidPrice;
201 |
202 | let checkPrice = buyerSetting["idAbBidExact"]
203 | ? bidPrice
204 | : isBid
205 | ? getBuyBidPrice(currentBid)
206 | : currentBid;
207 |
208 | if (isAutoBuyerActive && currentBid <= priceToBid) {
209 | writeToLog(
210 | "Bidding on outbidded item -> Bidding Price :" + checkPrice,
211 | idProgressAutobuyer
212 | );
213 | await buyPlayer(player, playerName, checkPrice, sellPrice);
214 | buyerSetting["idAbAddBuyDelay"] && (await wait(1));
215 | }
216 | };
217 |
--------------------------------------------------------------------------------
/app/views/layouts/Settings/SearchSettingsView.js:
--------------------------------------------------------------------------------
1 | import { generateToggleInput } from "../../../utils/uiUtils/generateToggleInput";
2 | import {
3 | idAbAddFilterGK,
4 | idAbMinRating,
5 | idAbMaxRating,
6 | idAbRandMinBidInput,
7 | idAbRandMinBidToggle,
8 | idAbRandMinBuyInput,
9 | idAbRandMinBuyToggle,
10 | idAbMaxSearchPage,
11 | idAddIgnorePlayers,
12 | idAddIgnorePlayersList,
13 | idRemoveIgnorePlayers,
14 | idAbIgnoreAllowToggle,
15 | idTooltip,
16 | idAbShouldSort,
17 | idAbSortBy,
18 | idAbSortOrder,
19 | idAbAddFilterBC,
20 | } from "../../../elementIds.constants";
21 | import { generateTextInput } from "../../../utils/uiUtils/generateTextInput";
22 | import { checkAndAppendOption } from "../../../utils/filterUtil";
23 | import { getValue, setValue } from "../../../services/repository";
24 | import { generateButton } from "../../../utils/uiUtils/generateButton";
25 | let playerInput;
26 |
27 | export const destoryPlayerInput = () => {
28 | playerInput.destroy();
29 | playerInput = null;
30 | };
31 |
32 | const updateAbSortBy = () => {
33 | const sortBy = $(`#${idAbSortBy}`).val() || "buy";
34 | const buyerSetting = getValue("BuyerSettings") || {};
35 | buyerSetting["idAbSortBy"] = sortBy;
36 | setValue("BuyerSettings", buyerSetting);
37 | };
38 |
39 | $(document).on({ change: updateAbSortBy }, `#${idAbSortBy}`);
40 |
41 | const playerIgnoreList = function () {
42 | playerInput = new UTPlayerSearchControl();
43 | const playerListId = `#${idAddIgnorePlayersList}`;
44 | const element = $(`
45 |
46 |
47 |
48 | Players List
49 |
50 |
51 |
52 | ${generateButton(
53 | idAddIgnorePlayers,
54 | "+",
55 | () => {
56 | const displayName = `${playerInput._playerNameInput.value}(${playerInput.selected.rating})`;
57 | const exists = checkAndAppendOption(
58 | playerListId,
59 | displayName
60 | );
61 | $(`${playerListId} option[value="${displayName}"]`).attr(
62 | "selected",
63 | true
64 | );
65 | if (!exists) {
66 | const buyerSetting = getValue("BuyerSettings") || {};
67 | const existingPlayersList =
68 | buyerSetting["idAddIgnorePlayersList"] || [];
69 | existingPlayersList.push({
70 | id: playerInput.selected.id,
71 | displayName,
72 | });
73 | buyerSetting["idAddIgnorePlayersList"] =
74 | existingPlayersList;
75 | setValue("BuyerSettings", buyerSetting);
76 | }
77 | },
78 | "btn-standard filterSync action-icons"
79 | )}
80 |
81 |
82 |
83 |
84 |
85 | Remove from Players List
86 |
87 |
88 |
89 |
92 | ${generateButton(
93 | idRemoveIgnorePlayers,
94 | "❌",
95 | () => {
96 | const playerName = $(`${playerListId} option`)
97 | .filter(":selected")
98 | .val();
99 | if (playerName != "Ignored Players List") {
100 | $(
101 | `${playerListId}` + ` option[value="${playerName}"]`
102 | ).remove();
103 | $(`${playerListId}`).prop("selectedIndex", 0);
104 | const buyerSetting = getValue("BuyerSettings") || {};
105 | let existingPlayersList =
106 | buyerSetting["idAddIgnorePlayersList"] || [];
107 | existingPlayersList = existingPlayersList.filter(
108 | ({ displayName }) => displayName != playerName
109 | );
110 | buyerSetting["idAddIgnorePlayersList"] =
111 | existingPlayersList;
112 | setValue("BuyerSettings", buyerSetting);
113 | }
114 | },
115 | "btn-standard filterSync font15 action-icons"
116 | )}
117 |
118 |
119 | `);
120 |
121 | $(playerInput.__root).insertBefore(element.find(`#${idAddIgnorePlayers}`));
122 | playerInput.init();
123 | playerInput._playerNameInput.setPlaceholder("Search Players");
124 | return element;
125 | };
126 |
127 | export const searchSettingsView = function () {
128 | const element =
129 | $(`
130 |
131 |
132 | ${generateToggleInput(
133 | "Ignore/Buy Players List",
134 | { idAbIgnoreAllowToggle },
135 | "(If toggled bot will only buy/bid the above players else bot will ignore the players when bidding/buying )",
136 | "BuyerSettings"
137 | )}
138 | ${generateTextInput(
139 | "Min Rating",
140 | 10,
141 | { idAbMinRating },
142 | "Minimum Player Rating",
143 | "BuyerSettings"
144 | )}
145 | ${generateTextInput(
146 | "Max Rating",
147 | 100,
148 | { idAbMaxRating },
149 | "Maximum Player Rating",
150 | "BuyerSettings"
151 | )}
152 | ${generateTextInput(
153 | "Search result page limit",
154 | 5,
155 | { idAbMaxSearchPage },
156 | "No of. pages bot should move forward before going back to page 1",
157 | "BuyerSettings"
158 | )}
159 | ${generateTextInput(
160 | "Max value of random min bid",
161 | 300,
162 | { idAbRandMinBidInput },
163 | "",
164 | "BuyerSettings"
165 | )}
166 | ${generateToggleInput(
167 | "Use random min bid",
168 | { idAbRandMinBidToggle },
169 | "",
170 | "BuyerSettings"
171 | )}
172 | ${generateTextInput(
173 | "Max value of random min buy",
174 | 300,
175 | { idAbRandMinBuyInput },
176 | "",
177 | "BuyerSettings"
178 | )}
179 | ${generateToggleInput(
180 | "Use random min buy",
181 | { idAbRandMinBuyToggle },
182 | "",
183 | "BuyerSettings"
184 | )}
185 | ${generateToggleInput(
186 | "SKIP GK",
187 | { idAbAddFilterGK },
188 | "(Skip all goalkeepers to buy / bid a card)",
189 | "BuyerSettings"
190 | )}
191 | ${generateToggleInput(
192 | "SKIP Basic Chemistry",
193 | { idAbAddFilterBC },
194 | "(Skip cards with basic chemistry to buy / bid a card)",
195 | "BuyerSettings"
196 | )}
197 | ${generateToggleInput(
198 | "Sort players",
199 | { idAbShouldSort },
200 | "",
201 | "BuyerSettings"
202 | )}
203 |
214 | ${generateToggleInput(
215 | "Order",
216 | { idAbSortOrder },
217 | "(Enabled = descending, Disabled = ascending)",
218 | "BuyerSettings"
219 | )}
220 |
`);
221 |
222 | const parentEl = element.find(".place-holder");
223 | playerIgnoreList().insertAfter(parentEl);
224 | return element;
225 | };
226 |
--------------------------------------------------------------------------------
/app/views/layouts/Settings/FilterSettingsView.js:
--------------------------------------------------------------------------------
1 | import {
2 | idAbDownloadFilter,
3 | idAbUploadFilter,
4 | idFilterDropdown,
5 | idAbNumberFilterSearch,
6 | idRunFilterSequential,
7 | idSelectedFilter,
8 | idAbToggleRunner,
9 | idBtnReport,
10 | idBtnActions,
11 | } from "../../../elementIds.constants";
12 | import { getValue, setValue } from "../../../services/repository";
13 | import { getUserFilters } from "../../../utils/dbUtil";
14 | import { uploadFilters, downloadFilters } from "../../../utils/filterSyncUtil";
15 | import { updateMultiFilterSettings } from "../../../utils/filterUtil";
16 | import { generateButton } from "../../../utils/uiUtils/generateButton";
17 | import { generateTextInput } from "../../../utils/uiUtils/generateTextInput";
18 | import { generateToggleInput } from "../../../utils/uiUtils/generateToggleInput";
19 | import {
20 | deleteFilter,
21 | loadFilter,
22 | saveFilterDetails,
23 | } from "../../../utils/userExternalUtil";
24 | import { createButton } from "../ButtonView";
25 | import { showPopUp } from "../../../utils/popupUtil";
26 |
27 | const filters = async () => {
28 | if (!getValue("filters")) {
29 | setValue("filters", (await getUserFilters()) || {});
30 | }
31 |
32 | let filters = getValue("filters");
33 |
34 | filters = Object.keys(filters)
35 | .sort()
36 | .reduce((obj, key) => {
37 | obj[key] = filters[key];
38 | return obj;
39 | }, {});
40 |
41 | return filters;
42 | };
43 | $(document).on(
44 | {
45 | change: updateMultiFilterSettings,
46 | click: updateMultiFilterSettings,
47 | touchend: updateMultiFilterSettings,
48 | },
49 | `#${idSelectedFilter}`
50 | );
51 |
52 | const handleToggle = (evt, key) => {
53 | let runSequentially = getValue(key);
54 | if (runSequentially) {
55 | runSequentially = false;
56 | $(evt.currentTarget).removeClass("toggled");
57 | } else {
58 | runSequentially = true;
59 | $(evt.currentTarget).addClass("toggled");
60 | }
61 | setValue(key, runSequentially);
62 | return runSequentially;
63 | };
64 |
65 | export const filterSettingsView = async function () {
66 | if (getValue("runSequentially")) {
67 | setValue("runSequentially", false);
68 | setTimeout(() => {
69 | $(`#${idRunFilterSequential}`).click();
70 | });
71 | }
72 | return `
73 |
74 |
80 |
81 | ${generateTextInput(
82 | "No. of search For each filter",
83 | getValue("fiterSearchCount") || 3,
84 | { idAbNumberFilterSearch },
85 | "(Count of searches performed before switching to another filter)",
86 | "CommonSettings",
87 | "number",
88 | null,
89 | "buyer-settings-field",
90 | (value) => setValue("fiterSearchCount", parseInt(value) || 3)
91 | )}
92 | ${generateToggleInput(
93 | "Switch filter sequentially",
94 | { idRunFilterSequential },
95 | "",
96 | "CommonSettings",
97 | "buyer-settings-field",
98 | (evt) => handleToggle(evt, "runSequentially")
99 | )}
100 |
101 | `;
102 | };
103 |
104 | const handleReportProblem = () => {
105 | showPopUp(
106 | [
107 | { labelEnum: atob("RGlzY29yZA==") },
108 | { labelEnum: atob("VHdpdHRlcg==") },
109 | { labelEnum: atob("R2l0aHVi") },
110 | ],
111 | atob("UmVwb3J0IGEgcHJvYmxlbQ=="),
112 | atob(
113 | "QmVsb3cgYXJlIHRoZSBsaXN0IG9mIHdheXMgdG8gcmVwb3J0IGEgcHJvYmxlbSA8YnIgLz5NYWtlIHN1cmUgdG8gZ28gdGhyb3VnaCB0aGUgPGEgaHJlZj0naHR0cHM6Ly95b3V0dWJlLmNvbS9wbGF5bGlzdD9saXN0PVBMR21LTWczYVJrWGpQUjVna2x4TXlxeHRoWW9vV0k1SUMnIHRhcmdldD0nX2JsYW5rJz55b3V0dWJlIHBsYXlsaXN0PC9hPiBpZiBhbnkgc2V0dGluZ3MgYXJlIHVuY2xlYXIgPGJyIC8+"
114 | ),
115 | (t) => {
116 | if (t === atob("R2l0aHVi")) {
117 | window.open(
118 | atob(
119 | "aHR0cHM6Ly9naXRodWIuY29tL2NoaXRoYWt1bWFyMTMvRlVULUF1dG8tQnV5ZXIvaXNzdWVz"
120 | ),
121 | atob("X2JsYW5r")
122 | );
123 | } else if (t === atob("RGlzY29yZA==")) {
124 | window.open(
125 | atob("aHR0cHM6Ly9kaXNjb3JkLmNvbS9pbnZpdGUvY2t0SFltcA=="),
126 | atob("X2JsYW5r")
127 | );
128 | } else if (t === atob("VHdpdHRlcg==")) {
129 | window.open(
130 | atob("aHR0cHM6Ly90d2l0dGVyLmNvbS9BbGdvc0Nr"),
131 | atob("X2JsYW5r")
132 | );
133 | }
134 | }
135 | );
136 | };
137 |
138 | export const filterHeaderSettingsView = async function (isTransferSearch) {
139 | const context = this;
140 | const filterId = isTransferSearch ? "transfer" : "";
141 | $(document).off("change", `#${idFilterDropdown}${filterId}`);
142 | $(document).on(
143 | {
144 | change: function () {
145 | const filterName = $(`#${idFilterDropdown}${filterId} option`)
146 | .filter(":selected")
147 | .val();
148 | loadFilter.call(context, filterName, isTransferSearch);
149 | },
150 | },
151 | `#${idFilterDropdown}${filterId}`
152 | );
153 |
154 | const rootHeader =
155 | $(`
156 | ${
157 | isPhone() && !isTransferSearch
158 | ? generateToggleInput(
159 | "Runner Mode",
160 | { idAbToggleRunner },
161 | "",
162 | "MisSettings",
163 | "runner",
164 | (evt) => {
165 | const isToggled = handleToggle(evt, "runnerToggle");
166 | $(".auto-buyer").toggleClass("displayNone");
167 | if (isToggled) {
168 | $(".filter-place").append($(`#${idSelectedFilter}`));
169 | } else {
170 | $(".teleporter").append($(`#${idSelectedFilter}`));
171 | }
172 | }
173 | )
174 | : ""
175 | }
176 | ${
177 | !isTransferSearch
178 | ? `
`
179 | : ""
180 | }
181 |
182 |
183 |
184 |
195 | ${
196 | !isTransferSearch
197 | ? generateButton(
198 | idAbUploadFilter,
199 | "⇧",
200 | () => {
201 | uploadFilters();
202 | },
203 | "filterSync",
204 | "Upload filters"
205 | )
206 | : ""
207 | }
208 | ${
209 | !isTransferSearch
210 | ? generateButton(
211 | idAbDownloadFilter,
212 | "⇩",
213 | () => {
214 | downloadFilters();
215 | },
216 | "filterSync",
217 | "Download filters"
218 | )
219 | : ""
220 | }
221 |
222 | ${
223 | !isTransferSearch
224 | ? `
`
225 | : ""
226 | }
227 |
`);
228 |
229 | !isTransferSearch && appendButtons.call(this, rootHeader, context);
230 | return rootHeader;
231 | };
232 |
233 | const appendButtons = function (rootHeader, context) {
234 | const buttons = rootHeader.find(`#${idBtnActions}`);
235 | const btnReport = rootHeader.find(`#${idBtnReport}`);
236 | buttons.append(
237 | createButton(
238 | "Delete Filter",
239 | () => deleteFilter.call(context),
240 | "call-to-action btn-delete-filter"
241 | ).__root
242 | );
243 | buttons.append(
244 | createButton(
245 | "Save Filter",
246 | function () {
247 | saveFilterDetails.call(this, context);
248 | },
249 | "call-to-action btn-save-filter"
250 | ).__root
251 | );
252 | btnReport.append(
253 | createButton(
254 | "Report a problem",
255 | () => handleReportProblem(),
256 | "call-to-action"
257 | ).__root
258 | );
259 | };
260 |
--------------------------------------------------------------------------------
/app/utils/searchUtil.js:
--------------------------------------------------------------------------------
1 | import { idProgressAutobuyer } from "../elementIds.constants";
2 | import { searchErrorHandler } from "../handlers/errorHandler";
3 | import {
4 | getDataSource,
5 | getValue,
6 | increAndGetStoreValue,
7 | setValue,
8 | } from "../services/repository";
9 | import { convertToSeconds, getRandNum } from "./commonUtil";
10 | import { checkRating } from "./futItemUtil";
11 | import { writeToLog } from "./logUtil";
12 | import { sendPinEvents } from "./notificationUtil";
13 | import { getBuyBidPrice, getSellBidPrice, roundOffPrice } from "./priceUtils";
14 | import { buyPlayer } from "./purchaseUtil";
15 | import { updateRequestCount } from "./statsUtil";
16 | import { sortPlayers } from "./playerUtil";
17 | import { getStatsValue } from "../handlers/statsProcessor";
18 | import { fetchPrices } from "../services/datasource";
19 |
20 | const currentBids = new Set();
21 |
22 | export const searchTransferMarket = function (buyerSetting) {
23 | return new Promise((resolve) => {
24 | const expiresIn = convertToSeconds(buyerSetting["idAbItemExpiring"]);
25 | const useRandMinBid = buyerSetting["idAbRandMinBidToggle"];
26 | const useRandMinBuy = buyerSetting["idAbRandMinBuyToggle"];
27 | const futBinBuyPercent = buyerSetting["idBuyFutBinPercent"] || 100;
28 | let currentPage = getValue("currentPage") || 1;
29 | const currentSearchPerMin = getStatsValue("searchPerMinuteCount");
30 | if (
31 | currentSearchPerMin > 15 &&
32 | (buyerSetting["idAbOverSearchWarning"] ||
33 | buyerSetting["idAbOverSearchWarning"] === undefined)
34 | ) {
35 | writeToLog("--------------------", idProgressAutobuyer);
36 | writeToLog(
37 | "!!! More than 15 searches performed in last minute, increase your wait time",
38 | idProgressAutobuyer
39 | );
40 | writeToLog("--------------------", idProgressAutobuyer);
41 | }
42 | const playersList = new Set(
43 | (buyerSetting["idAddIgnorePlayersList"] || []).map(({ id }) => id)
44 | );
45 | const dataSource = getDataSource();
46 | let bidPrice = buyerSetting["idAbMaxBid"];
47 | let userBuyNowPrice = buyerSetting["idAbBuyPrice"];
48 | let useFutBinPrice =
49 | buyerSetting["idBuyFutBinPrice"] || buyerSetting["idAbBidFutBin"];
50 |
51 | if (!userBuyNowPrice && !bidPrice && !useFutBinPrice) {
52 | writeToLog(
53 | "skip search >>> (No Buy or Bid Price given)",
54 | idProgressAutobuyer
55 | );
56 | return resolve();
57 | }
58 |
59 | sendPinEvents("Transfer Market Search");
60 | updateRequestCount();
61 | let searchCriteria = this._viewmodel.searchCriteria;
62 | if (useRandMinBid)
63 | searchCriteria.minBid = roundOffPrice(
64 | getRandNum(0, buyerSetting["idAbRandMinBidInput"])
65 | );
66 | if (useRandMinBuy)
67 | searchCriteria.minBuy = roundOffPrice(
68 | getRandNum(0, buyerSetting["idAbRandMinBuyInput"])
69 | );
70 | services.Item.clearTransferMarketCache();
71 |
72 | services.Item.searchTransferMarket(searchCriteria, currentPage).observe(
73 | this,
74 | async function (sender, response) {
75 | if (response.success) {
76 | setValue("searchFailedCount", 0);
77 | let validSearchCount = true;
78 | writeToLog(
79 | `Found ${response.data.items.length} items, page - ${currentPage} => (minbid: ${searchCriteria.minBid} minbuy:${searchCriteria.minBuy})`,
80 | idProgressAutobuyer
81 | );
82 |
83 | if (response.data.items.length > 0) {
84 | writeToLog("--------------------", idProgressAutobuyer);
85 | currentPage === 1 &&
86 | sendPinEvents("Transfer Market Results - List View");
87 | if (useFutBinPrice && response.data.items[0].type === "player") {
88 | await fetchPrices(response.data.items);
89 | }
90 | }
91 |
92 | if (response.data.items.length > buyerSetting["idAbSearchResult"]) {
93 | validSearchCount = false;
94 | }
95 |
96 | let maxPurchases = buyerSetting["idAbMaxPurchases"] || 1;
97 |
98 | if (buyerSetting["idAbShouldSort"])
99 | response.data.items = sortPlayers(
100 | response.data.items,
101 | buyerSetting["idAbSortBy"] || "buy",
102 | buyerSetting["idAbSortOrder"]
103 | );
104 | for (
105 | let i = response.data.items.length - 1;
106 | i >= 0 && getValue("autoBuyerActive");
107 | i--
108 | ) {
109 | let player = response.data.items[i];
110 | let auction = player._auction;
111 | let expires = services.Localization.localizeAuctionTimeRemaining(
112 | auction.expires
113 | );
114 | let type = player.type;
115 | let { id } = player._metaData || {};
116 | let playerRating = parseInt(player.rating);
117 |
118 | if (useFutBinPrice && type === "player") {
119 | const existingValue = getValue(
120 | `${player.definitionId}_${dataSource.toLowerCase()}_price`
121 | );
122 | if (existingValue && existingValue.price) {
123 | const futBinBuyPrice = roundOffPrice(
124 | (existingValue.price * futBinBuyPercent) / 100
125 | );
126 | userBuyNowPrice = futBinBuyPrice;
127 | if (buyerSetting["idAbBidFutBin"]) {
128 | bidPrice = futBinBuyPrice;
129 | }
130 | } else {
131 | writeToLog(
132 | `Price unavailable for ${player._staticData.name}`,
133 | idProgressAutobuyer
134 | );
135 | continue;
136 | }
137 | }
138 |
139 | let buyNowPrice = auction.buyNowPrice;
140 | let currentBid = auction.currentBid || auction.startingBid;
141 | let isBid = auction.currentBid;
142 |
143 | let priceToBid = buyerSetting["idAbBidExact"]
144 | ? bidPrice
145 | : isBid
146 | ? getSellBidPrice(bidPrice)
147 | : bidPrice;
148 |
149 | let checkPrice = buyerSetting["idAbBidExact"]
150 | ? priceToBid
151 | : isBid
152 | ? getBuyBidPrice(currentBid)
153 | : currentBid;
154 |
155 | let usersellPrice = buyerSetting["idAbSellPrice"];
156 | let minRating = buyerSetting["idAbMinRating"];
157 | let maxRating = buyerSetting["idAbMaxRating"];
158 | let playerName = player._staticData.name;
159 |
160 | const shouldCheckRating = minRating || maxRating;
161 |
162 | const isValidRating =
163 | !shouldCheckRating ||
164 | checkRating(playerRating, minRating, maxRating);
165 |
166 | const logWrite = writeToLogClosure(
167 | `${playerName}(${playerRating}) Price: ${buyNowPrice} time: ${expires}`
168 | );
169 |
170 | if (
171 | (!buyerSetting["idAbIgnoreAllowToggle"] && playersList.has(id)) ||
172 | (buyerSetting["idAbIgnoreAllowToggle"] && !playersList.has(id))
173 | ) {
174 | logWrite("(Ignored player)");
175 | continue;
176 | }
177 |
178 | if (!validSearchCount) {
179 | logWrite("(Exceeded search result threshold)");
180 | continue;
181 | }
182 |
183 | if (maxPurchases < 1) {
184 | break;
185 | }
186 |
187 | if (!player.preferredPosition && buyerSetting["idAbAddFilterGK"]) {
188 | logWrite("(is a Goalkeeper)");
189 | continue;
190 | }
191 |
192 | if (
193 | (player.playStyle === DEFAULT_PLAYSTYLE_ID ||
194 | player.playStyle ===
195 | ItemSubType.TRAINING_PLAYERSTYLE_GOALKEEPER_5) &&
196 | buyerSetting["idAbAddFilterBC"]
197 | ) {
198 | logWrite("(is a Basic Chemistry)");
199 | continue;
200 | }
201 |
202 | if (!isValidRating) {
203 | logWrite("(rating does not fit criteria)");
204 | continue;
205 | }
206 |
207 | if (currentBids.has(auction.tradeId)) {
208 | logWrite("(Cached Item)");
209 | continue;
210 | }
211 |
212 | const userCoins = services.User.getUser().coins.amount;
213 | if (
214 | (!bidPrice && userCoins < buyNowPrice) ||
215 | (bidPrice && userCoins < checkPrice)
216 | ) {
217 | logWrite("(Insufficient coins to buy/bid)");
218 | continue;
219 | }
220 |
221 | if (buyNowPrice <= userBuyNowPrice) {
222 | logWrite("attempt buy: " + buyNowPrice);
223 | maxPurchases--;
224 | currentBids.add(auction.tradeId);
225 | await buyPlayer(
226 | player,
227 | playerName,
228 | buyNowPrice,
229 | usersellPrice,
230 | true,
231 | auction.tradeId
232 | );
233 | continue;
234 | }
235 |
236 | if (bidPrice && currentBid <= priceToBid) {
237 | if (auction.expires > expiresIn) {
238 | logWrite("(Waiting for specified expiry time)");
239 | continue;
240 | }
241 | logWrite("attempt bid: " + checkPrice);
242 | currentBids.add(auction.tradeId);
243 | maxPurchases--;
244 | await buyPlayer(
245 | player,
246 | playerName,
247 | checkPrice,
248 | usersellPrice,
249 | checkPrice === buyNowPrice,
250 | auction.tradeId
251 | );
252 | continue;
253 | }
254 |
255 | if (
256 | (userBuyNowPrice && buyNowPrice > userBuyNowPrice) ||
257 | (bidPrice && currentBid > priceToBid)
258 | ) {
259 | logWrite(
260 | `BuyPrice: ${
261 | userBuyNowPrice || priceToBid
262 | } (higher than specified buy/bid price)`
263 | );
264 | continue;
265 | }
266 | logWrite("(No Actions Required)");
267 | }
268 | } else {
269 | searchErrorHandler(
270 | response,
271 | buyerSetting["idAbSolveCaptcha"],
272 | buyerSetting["idAbCloseTabToggle"]
273 | );
274 | }
275 | sendPinEvents("Transfer Market Search");
276 |
277 | if (
278 | currentPage < buyerSetting["idAbMaxSearchPage"] &&
279 | response.data.items.length === 21
280 | ) {
281 | increAndGetStoreValue("currentPage");
282 | } else {
283 | setValue("currentPage", 1);
284 | }
285 | resolve();
286 | }
287 | );
288 | });
289 | };
290 |
291 | const writeToLogClosure = (playerName) => {
292 | return (actionTxt) => {
293 | writeToLog(playerName + " " + actionTxt, idProgressAutobuyer);
294 | };
295 | };
296 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # FUT Trader
2 |
3 | [![Contributors][contributors-shield]][contributors-url]
4 | [![Forks][forks-shield]][forks-url]
5 | [![Stargazers][stars-shield]][stars-url]
6 | [![Issues][issues-shield]][issues-url]
7 | []()
8 |
9 |
10 |
FUT Trader
11 |
12 |
You can install the script from Chrome Store
13 |
14 |
15 | Trader from FIFA Ultimate Team Webapp!
16 |
17 |
18 | Report Bug
19 | ·
20 | Request Feature
21 | ·
22 | Subscribe
23 |
24 | # Must Read :no_entry_sign:
25 |
26 | These tool is developed to demonstrate how someone can develop script to break our web application by automating stuffs and only for learning purpose.
27 |
28 | EA might (soft) ban from using transfer market in web app for using this tool. Continuously soft ban might lead to permanent ban as well. Also use of tools like this to gain advantage over other players is not ethically right.
29 |
30 | Use this tool at your own risk, any developers contributing to this repo won’t held responsibility if your account gets banned.
31 |
32 |
33 |
34 |
35 |
36 | ## Table of Contents
37 |
38 | - [Installation](#installation)
39 | - [AutoSolve Captcha SetUp](#Captcha)
40 | - [Usage](#Usage)
41 | - [Prerequisites](#prerequisites)
42 | - [Telegram Installation Guide](#Telegram-Installation-Guide)
43 | - [Roadmap](#Roadmap)
44 | - [Developer Guide](#Developer-Guide)
45 | - [Contributing](#contributing)
46 | - [Contact](#contact)
47 |
48 |
49 |
50 | ## Installation
51 |
52 | - Add Tamper Monkey Extenstion to your Browser - [Link](https://chrome.google.com/webstore/detail/tampermonkey/dhdgffkkebhmkfjojejmpbldmpobfkfo?hl=en-GB).
53 | - Click on fut-trader.user.js from - [Latest Release](https://github.com/ckalgos/FUT-Trader/releases/).
54 | - Then click on Install/Update.
55 | - Installation And Demo - [Video Guide](https://www.youtube.com/watch?v=WATch4hxhtk).
56 |
57 | Now in Ultimate Team Web App, new menu will be added as Trader.
58 |
59 | Using mobile? No problem, you can get the app for your os below
60 |
61 | | Android | iOS |
62 | | :--------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------: |
63 | | [
](https://play.google.com/store/apps/details?id=com.fut.market.alert) | [
](https://apps.apple.com/us/app/fut-market-alert/id1590505179) |
64 |
65 | ## Captcha
66 |
67 | ### AutoSolve Captcha Setup
68 |
69 | - Follow this [video](https://www.youtube.com/watch?v=9p_IMe52LBo) if you are not sure how to set it up.
70 |
71 |
72 |
73 | ## Usage
74 |
75 | ### Trader Settings
76 |
77 | ### Sell Price
78 |
79 | - If specified the trader will list the bought item for the specified price.
80 | - The tool will list all the cards in transfer target, make sure to move your cards to the club before running the tool to avoid losing the card.
81 | - Give Sell Price as -1 to move to transfer list without selling.
82 |
83 | ### Buy Price
84 |
85 | - If specified the trader will buy the card matching the search critieria for the price less than or equal to specified price.
86 |
87 | ### Bid Price
88 |
89 | - If specified the trader will bid on the card matching the search critieria for the price less than or equal to specified price.
90 |
91 | ### No. of cards to buy
92 |
93 | - Number of cards the trader should buy before auto stopping.
94 | - Default Value (`10`).
95 |
96 | ### Bid Exact Price
97 |
98 | - By default tool will bid for the lowest possible price and gradually increase the bid , if this flag is enabled bot will directly bid for the specified bid price.
99 |
100 | ### Find Sale Price
101 |
102 | - If toggled will use FUTBIN price api to get the player price.
103 |
104 | ### Sell Price Percent
105 |
106 | - When find sale price is toggled, this field is to specify the sale price from the percent of FUTBIN Price.
107 | - Default Value (`100`).
108 |
109 | ### Bid items expiring in:
110 |
111 | - If specified tool will only bid on items expiring in the given time range.
112 | - Default Value (`1H`) (S for seconds, M for Minutes, H for hours).
113 |
114 | ### Relist Unsold Items:
115 |
116 | - If enabled bot will periodically check and relist expired item for the previous specified price
117 |
118 | ### \* Note : This will relist all expired items , not only the item which bot list. So check the Transfer List before enabling this to avoid losing cards
119 |
120 | ### Wait Time
121 |
122 | - The trader will wait for the specified time before making the next search request.
123 | - Default Value (`7 - 15`).
124 |
125 | ### Clear sold count
126 |
127 | - The trader will clear all the sold items from transfer list when the count exceeds the specified value.
128 | - Default Value (`10`).
129 |
130 | ### Rating Threshold
131 |
132 | - Will only list the card if the rating of the card is below this value.
133 | - Default Value (`100`).
134 |
135 | ### Max purchases per search request
136 |
137 | - Indicates the count of cards the tool should buy or bid from the results of each request.
138 | - Default Value (`3`).
139 |
140 | ### Stop After
141 |
142 | - If specified the tool will automatically stop after running the tool foe the specified interval.
143 | - Default Value (`1H`) (S for seconds, M for Minutes, H for hours).
144 |
145 | ### Pause For
146 |
147 | - The parameter has a dependency on Cycle Amount
148 | - The tool will pause for the specified interval, if the the number of search request in the given cycle matches the specified cycle amount.
149 | - Default Value (`0-0S`) (S for seconds, M for Minutes, H for hours).
150 |
151 | ### Pause Cycle
152 |
153 | - Indicates the amount of search request to be made before pausing the tool.
154 | - Default Value (`10`).
155 |
156 | ### Min Rating
157 |
158 | - If specified tool will bid only on items which has rating greater or equal to this value.
159 | - Default Value (`10`).
160 |
161 | ### Max Rating
162 |
163 | - If specified tool will bid only on items which has rating lesser or equal to this value.
164 | - Default Value (`100`).
165 |
166 | ### Delay To Add
167 |
168 | - If add delay after buy is enabled, this field specifies the wait duration.
169 | - Default Value (`1S`) (S for seconds, M for Minutes, H for hours).
170 |
171 | ### Add Delay After Buy
172 |
173 | - If enabled tool will wait for the specified time after trying to buy/bid on cards.
174 |
175 | ### Max value of random min bid
176 |
177 | - If use random min bid is enabled , this field specifies the maximum value till which min bid can be generated.
178 | - Default Value (`300`).
179 |
180 | ### Max value of random min buy
181 |
182 | - If use random min buy is enabled , this field specifies the maximum value till which min buy can be generated.
183 | - Default Value (`300`).
184 |
185 | ### Use Random Min Bid
186 |
187 | - If enabled tool will randomize min bid for each search to avoid cached result.
188 |
189 | ### Use Random Min Buy
190 |
191 | - If enabled tool will randomize min buy for each search to avoid cached result.
192 |
193 | ### Skip GK
194 |
195 | - If enabled tool will skip bidding/buying GK Cards.
196 |
197 | ### Close On Captcha Trigger
198 |
199 | - If enabled tool will close the web app when Captcha gets triggered.
200 |
201 | ### Delay After Buy
202 |
203 | - If enabled tool will add 1 second delay after each buy request.
204 |
205 | ### Error Codes to stop bot
206 |
207 | - List of error code on which bot should stop, value should be in csv format.
208 | - Ex - 421,461,512
209 |
210 | ### Sound Notification
211 |
212 | - If enabled tool will gives sound notification for actions like buy card / captcha trigger etc...
213 |
214 | ## Prerequisites
215 |
216 | - To use this tool, the user should have access to the transfer market.
217 | - Hence play the required number of games to get access to the transfer market before trying this tool.
218 |
219 |
220 |
221 | ## Telegram-Installation-Guide
222 |
223 | - Install Telegram
224 | - Add @BotFather as a contact in telegram
225 | - Send BotFather /newbot
226 | - Type in your individual details like name etc.
227 | - Follow the prompts, and finally copy it’s HTTP API Token
228 | - Add your bot as a contact
229 | - Send /start to your bot
230 | - Visit this URL. https://api.telegram.org/botXXX:YYYYY/getUpdates (replace the XXX: YYYYY with your BOT HTTP API Token you just got from the Telegram B otFather)
231 | - Here you can find your chat id (this process can take some minutes) --> e.g. "chat":{"id":133333338,"first_name":"John","last_name":"Player"
232 | - Now you can add your bot token and chat id at the notification settings inside the bot GUI (near the Bottom)
233 |
234 |
235 |
236 | ## Roadmap
237 |
238 | See the [open issues](https://github.com/ckalgos/FUT-Trader/issues) for a list of proposed features (and known issues).
239 |
240 | ## 💬 Community
241 |
242 | If you are looking for help or any new feature request, join our discord group
243 |
244 |
245 |
246 |
Join
247 |
248 |
249 |
250 | ## Developer-Guide
251 |
252 |
Join this discord channel
253 |
254 |
255 |
256 | ## Contributing
257 |
258 | Any contributions you make are **greatly appreciated**.
259 |
260 | 1. Fork the Project
261 | 2. Create your Feature Branch (`git checkout -b feature/FeatureBranch`)
262 | 3. Commit your Changes (`git commit -m 'Add some FeatureBranch'`)
263 | 4. Push to the Branch (`git push origin feature/FeatureBranch`)
264 | 5. Open a Pull Request
265 |
266 |
267 |
268 | ## Contact
269 |
270 | Instagram - [@Instagram](https://www.instagram.com/ckalgos/) - ckalgos@gmail.com
271 |
272 | Project Link: [https://github.com/ckalgos/FUT-Trader](https://github.com/ckalgos/FUT-Trader)
273 |
274 |
275 |
276 | [contributors-shield]: https://img.shields.io/github/contributors/ckalgos/FUT-Trader.svg?style=flat-square
277 | [contributors-url]: https://github.com/ckalgos/FUT-Trader/graphs/contributors
278 | [forks-shield]: https://img.shields.io/github/forks/ckalgos/FUT-Trader.svg?style=flat-square
279 | [forks-url]: https://github.com/ckalgos/FUT-Trader/network/members
280 | [stars-shield]: https://img.shields.io/github/stars/ckalgos/FUT-Trader.svg?style=flat-square
281 | [stars-url]: https://github.com/ckalgos/FUT-Trader/stargazers
282 | [issues-shield]: https://img.shields.io/github/issues/ckalgos/FUT-Trader.svg?style=flat-square
283 | [issues-url]: https://github.com/ckalgos/FUT-Trader/issues
284 |
--------------------------------------------------------------------------------