runnan

SBC Autofill with player swapping, AutoAll, and ForeverRun (423 & 422) with Retry Logic

Bu betiği kurabilmeniz için Tampermonkey, Greasemonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

You will need to install an extension such as Tampermonkey to install this script.

Bu betiği kurabilmeniz için Tampermonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği kurabilmeniz için Tampermonkey ya da Userscripts gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği indirebilmeniz için ayrıca Tampermonkey gibi bir eklenti kurmanız gerekmektedir.

Bu komut dosyasını yüklemek için bir kullanıcı komut dosyası yöneticisi uzantısı yüklemeniz gerekecek.

(Zaten bir kullanıcı komut dosyası yöneticim var, kurmama izin verin!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(Zateb bir user-style yöneticim var, yükleyeyim!)

// ==UserScript==
// @name         runnan
// @namespace    http://tampermonkey.net/
// @version      3.2.0
// @description  SBC Autofill with player swapping, AutoAll, and ForeverRun (423 & 422) with Retry Logic
// @license      MIT
// @match        https://www.ea.com/ea-sports-fc/ultimate-team/web-app/*
// @match        https://www.easports.com/*/ea-sports-fc/ultimate-team/web-app/*
// @match        https://www.ea.com/*/ea-sports-fc/ultimate-team/web-app/*
// @run-at       document-end
// ==/UserScript==

/*
 * Script Usage Disclaimer
 * Use at your own risk.
 */
(function () {
    'use strict';
    let page = unsafeWindow;
    let stopRequested = false;

    // Utility: sleep function
    const sleep = ms => new Promise(res => setTimeout(res, ms));

    // Utility: simulate click on element
    function simulateClick(el) {
        if (!el) {
            console.log('[Runnan] Element not found for click');
            return false;
        }
        const r = el.getBoundingClientRect();
        ['mousedown', 'mouseup', 'click'].forEach(t =>
            el.dispatchEvent(new MouseEvent(t, {
                bubbles: true, cancelable: true,
                clientX: r.left + r.width / 2,
                clientY: r.top + r.height / 2,
                button: 0
            }))
        );
        return true;
    }

    // Utility: wait for element to appear
    function waitForElement(selector, timeout = 5000) {
        return new Promise(resolve => {
            const start = Date.now();
            (function poll() {
                const el = document.querySelector(selector);
                if (el) return resolve(el);
                if (Date.now() - start > timeout) return resolve(null);
                setTimeout(poll, 200);
            })();
        });
    }

    // Utility: Find button by text
    function findButtonByText(textArray) {
        const buttons = Array.from(document.querySelectorAll('button, span.btn-text'));
        for (let el of buttons) {
            const txt = el.textContent.trim();
            // Check if text matches AND element is visible
            if (textArray.includes(txt) && el.offsetParent !== null) {
                return el.tagName === 'BUTTON' ? el : el.closest('button');
            }
        }
        return null;
    }

    // Utility: Shuffle array
    function shuffleArray(array) {
        for (let i = array.length - 1; i > 0; i--) {
            const j = Math.floor(Math.random() * (i + 1));
            [array[i], array[j]] = [array[j], array[i]];
        }
        return array;
    }

    // --- Core Logic ---

    // 1. AutoAll Function
    // 1. AutoAll Function
    async function sbcAutoAll(challengeIndex = 0, sbcId = null) {
        // Retry loop control
        const maxRetries = 3;
        let attempt = 0;

        while (attempt <= maxRetries) {
            if (stopRequested) return false;
            attempt++;
            console.log(`[Runnan] Starting AutoAll... Attempt ${attempt}/${maxRetries + 1}`);

            // Step A: Autofill
            const autofillBtn = findButtonByText(['SBC squad autofill', 'SBC方案填充']);
            if (autofillBtn) {
                console.log('[Runnan] Clicking Autofill');
                simulateClick(autofillBtn);
                await sleep(1000);

                // Handling for input SBC URL
                // Handling for input SBC URL
                if (sbcId === '423' && challengeIndex === 1) {
                    // Check for input field (Wait up to 2 seconds)
                    // Use more specific selector based on the dialog structure provided
                    const inputSelector = '.ea-dialog-view--body input';
                    const input = await waitForElement(inputSelector, 2000);

                    if (input) {
                        let urlToFill = '';
                        const urls = [
                            // 'https://www.futbin.com/26/squad/100471706/sbc',
                            'https://www.futbin.com/26/squad/100490049/sbc',
                            'https://www.futbin.com/26/squad/100529570/sbc'
                        ];
                        urlToFill = urls[Math.floor(Math.random() * urls.length)];

                        console.log(`[Runnan] Inputting URL: ${urlToFill}`);

                        // Robust Input Setting for Frameworks (React/Vue/etc)
                        input.focus();
                        await sleep(50);

                        // Simulate keydown
                        input.dispatchEvent(new KeyboardEvent('keydown', { bubbles: true, cancelable: true, key: 'a' }));

                        const nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value").set;
                        nativeInputValueSetter.call(input, urlToFill);

                        input.dispatchEvent(new Event('input', { bubbles: true }));
                        await sleep(50);

                        // Simulate keyup
                        input.dispatchEvent(new KeyboardEvent('keyup', { bubbles: true, cancelable: true, key: 'a' }));

                        input.dispatchEvent(new Event('change', { bubbles: true }));
                        await sleep(50);
                        input.blur();

                        await sleep(500);
                    } else {
                        console.log('[Runnan] Input field not found (skipped URL fill)');
                    }
                }

                const confirmBtn = await waitForElement('.ea-dialog-view button.btn-standard.primary', 3000);
                if (confirmBtn) {
                    console.log('[Runnan] Clicking Confirm (Autofill Dialog)');
                    simulateClick(confirmBtn);
                    await sleep(5000);

                    // Check for submit button immediately after autofill
                    console.log('[Runnan] Checking for early submit...');
                    const submitBtnSelector = 'body > main > section > section > div.ut-navigation-container-view--content > div > div > div > div.ut-draggable > button.ut-squad-tab-button-control.actionTab.right.primary';
                    let submitBtn = document.querySelector(submitBtnSelector);
                    if (!submitBtn) {
                        submitBtn = findButtonByText(['Submit', 'Submit SBC', '提交', 'Submit Squad']);
                    }

                    if (submitBtn && !submitBtn.classList.contains('disabled')) {
                        console.log('[Runnan] Squad ready after autofill. Submitting early...');
                        simulateClick(submitBtn);
                        await sleep(1000);

                        // Check for "Precious Player" / Warning dialog
                        const dialog = document.querySelector('.ea-dialog-view--body');
                        if (dialog) {
                            const buttons = Array.from(dialog.querySelectorAll('button'));
                            const continueBtn = buttons.find(b => b.textContent.includes('继续') || b.textContent.includes('Continue'));
                            if (continueBtn) {
                                console.log('[Runnan] Warning dialog detected (Early Submit). Clicking Continue.');
                                simulateClick(continueBtn);
                                await sleep(3000);
                            }
                        } else {
                            await sleep(2000);
                        }
                        return true; // Early success
                    }
                } else {
                    console.log('[Runnan] Confirm button not found in dialog.');
                }
            } else {
                console.log('[Runnan] Autofill button not found (might be already filled or wrong page)');
            }

            // Step B: Loop Players (Random Order)
            // Slots 2 to 12 correspond to nth-child indices
            const slots = Array.from({ length: 11 }, (_, k) => k + 3);
            const shuffledSlots = shuffleArray(slots);

            for (let i of shuffledSlots) {
                if (stopRequested) return false;
                console.log(`[Runnan] Processing slot ${i - 2}/11`);

                // Check submit button inside loop
                // Check if submit button is available and enabled
                const submitBtnSelectorLoop = 'body > main > section > section > div.ut-navigation-container-view--content > div > div > div > div.ut-draggable > button.ut-squad-tab-button-control.actionTab.right.primary';
                let submitBtnLoop = document.querySelector(submitBtnSelectorLoop);
                if (!submitBtnLoop) {
                    submitBtnLoop = findButtonByText(['Submit', 'Submit SBC', '提交', 'Submit Squad']);
                }

                if (submitBtnLoop && !submitBtnLoop.classList.contains('disabled')) {
                    console.log('[Runnan] Submit button enabled during loop. Submitting...');
                    simulateClick(submitBtnLoop);
                    await sleep(1000);

                    // Check for warning dialog
                    const dialog = document.querySelector('.ea-dialog-view--body');
                    if (dialog) {
                        const buttons = Array.from(dialog.querySelectorAll('button'));
                        const continueBtn = buttons.find(b => b.textContent.includes('继续') || b.textContent.includes('Continue'));
                        if (continueBtn) {
                            console.log('[Runnan] Warning dialog detected (Loop Submit). Clicking Continue.');
                            simulateClick(continueBtn);
                            await sleep(3000);
                        }
                    } else {
                        await sleep(2000);
                    }
                    return true; // Success, break loop
                }

                const playerSelector = `body > main > section > section > div.ut-navigation-container-view--content > div > div > div > div.ut-draggable > div.ut-squad-pitch-view.sbc > div:nth-child(${i})`;
                const playerSlot = document.querySelector(playerSelector);
                if (!playerSlot) {
                    // If selector fails, it might be a different formation or already filled?
                    continue;
                }

                simulateClick(playerSlot);
                await sleep(800);

                // Check "Direct Purchase" button immediately
                const directBuyBtn = findButtonByText(['直接购买此球员', 'Direct Purchase', 'Buy Now']);

                if (!directBuyBtn) {
                    // console.log(`[Runnan] Slot ${i - 1}: No direct purchase option (Already owned?). Skipping.`);
                    // Reduced log spam
                    continue;
                }

                // If we are here, we don't have the player.
                // Try Swap
                let swapped = false;
                // Specific selector from user REMOVED
                // const swapSelector = "body > main > section > section > div.ut-navigation-container-view--content > div > div > section > div.ut-navigation-container-view--content > div > div.DetailPanel > div.fsu-substitutionBox > div:nth-child(2) > button:nth-child(3)";
                // let swapBtn = document.querySelector(swapSelector);

                // if (!swapBtn) {
                const swapBtn = findButtonByText(['Swap Meets Requirements Players', '替换为满足需求球员', '满需求']);
                // }
                if (swapBtn) {
                    simulateClick(swapBtn);
                    await sleep(1000);

                    // Existing Swap Logic
                    const listBase = 'body > main > section > section > div.ut-navigation-container-view--content > div > div > section > div.ut-navigation-container-view--content > div > div.paginated-item-list.ut-pinned-list > ul > li';
                    for (let j = 1; j <= 5; j++) {
                        const itemBtn = document.querySelector(`${listBase}:nth-child(${j}) > button`);
                        const ratingEl = document.querySelector(`${listBase}:nth-child(${j}) > div > div.entityContainer > div.small.player.item > div.ut-item-view--main.ut-item-view > div > div.rating`);
                        const fsuLocked = document.querySelector(`${listBase}:nth-child(${j}) > div > div.entityContainer > div.name.fsulocked`);
                        const academyGraduate = document.querySelector(`${listBase}:nth-child(${j}) > div > div.entityContainer > div.small.player.item > div.ut-item-player-state-indicator-view.academy-graduate`);

                        if (!itemBtn) break;
                        if (fsuLocked || academyGraduate) continue;

                        if (ratingEl) {
                            const rating = parseInt(ratingEl.textContent, 10);
                            if (rating < 84) {
                                console.log(`[Runnan] Swapping with item ${j} (Rating: ${rating})`);
                                simulateClick(itemBtn);
                                swapped = true;
                                await sleep(1000);
                                break;
                            }
                        }
                    }
                }

                if (swapped) continue;

                // If Swap failed, try Direct Purchase if cheap
                // Force back to detail view if we were in swap list
                if (swapBtn) {
                    simulateClick(playerSlot);
                    await sleep(800);
                }

                // Now check price and buy
                const directBuyBtnAfter = findButtonByText(['直接购买此球员', 'Direct Purchase', 'Buy Now']);
                if (directBuyBtnAfter) {
                    const subtext = directBuyBtnAfter.querySelector('.btn-subtext.currency-coins');
                    if (subtext) {
                        const price = parseInt(subtext.textContent.replace(/,/g, ''), 10);
                        if (price <= 1200) {
                            console.log(`[Runnan] Buying player for ${price}`);
                            simulateClick(directBuyBtnAfter);
                            await sleep(4000); // Wait longer for buy

                            // Check for buy confirmation modal if it exists?
                            const buyConfirm = await waitForElement('.dialog-body button', 500);
                            if (buyConfirm && (buyConfirm.textContent.includes('OK') || buyConfirm.textContent.includes('Yes') || buyConfirm.textContent.includes('确定'))) {
                                simulateClick(buyConfirm);
                                await sleep(2000);
                            }
                        } else {
                            console.log(`[Runnan] Price ${price} > 1200. Skipping.`);
                        }
                    }
                }
            }

            // Step C: Submit
            console.log('[Runnan] Checking submit button...');
            const submitBtnSelector = 'body > main > section > section > div.ut-navigation-container-view--content > div > div > div > div.ut-draggable > button.ut-squad-tab-button-control.actionTab.right.primary';
            let submitBtn = document.querySelector(submitBtnSelector);

            // Fallback: find by text if selector failed
            if (!submitBtn) {
                submitBtn = findButtonByText(['Submit', 'Submit SBC', '提交', 'Submit Squad']);
            }

            if (submitBtn && !submitBtn.classList.contains('disabled')) {
                console.log('[Runnan] Submitting!');
                simulateClick(submitBtn);
                await sleep(1000);

                // Check for "Precious Player" / Warning dialog
                const dialog = document.querySelector('.ea-dialog-view--body');
                if (dialog) {
                    const buttons = Array.from(dialog.querySelectorAll('button'));
                    const continueBtn = buttons.find(b => b.textContent.includes('继续') || b.textContent.includes('Continue'));
                    if (continueBtn) {
                        console.log('[Runnan] Warning dialog detected. Clicking Continue.');
                        simulateClick(continueBtn);
                        await sleep(3000); // Wait for actual submission after confirmation
                    }
                } else {
                    await sleep(2000); // Wait remaining time if no dialog
                }

                return true; // Success
            } else {
                console.log('[Runnan] Submit button disabled or not found.');
                // If attempt < maxRetries, loop continues (retries)
                if (attempt <= maxRetries) {
                    console.log(`[Runnan] Retrying (${attempt}/${maxRetries})...`);
                    await sleep(2000);
                }
            }
        }

        console.log('[Runnan] AutoAll failed after all retries.');
        return false; // Failed
    }

    // 2. ForeverRun Function
    async function foreverRun(sbcId) {
        console.log(`[Runnan] Starting ForeverRun Loop for SBC ${sbcId}`);
        stopRequested = false;

        while (!stopRequested) {
            // 2.1 Click SBC by ID
            let sbcBtn = document.querySelector(`button[data-sbcid="${sbcId}"]`);

            // Fallback for 311 if not found via data attribute
            if (!sbcBtn && sbcId === '423') {
                sbcBtn = document.querySelector('body > main > section > section > div.ut-navigation-bar-view.navbar-style-landscape.currency-purchase > div.fsu-navsbc > button:nth-child(1)');
            }

            if (!sbcBtn) {
                console.log(`[Runnan] SBC ${sbcId} button not found. Please navigate to the SBC menu.`);
                await sleep(2000);
            } else {
                simulateClick(sbcBtn);
                await sleep(2000);
            }

            // 2.2 Loop 4 children
            for (let i = 1; i <= 4; i++) {
                if (stopRequested) break;
                console.log(`[Runnan] ForeverRun: Challenge ${i}/4`);

                const challengeSelector = `div.ut-sbc-challenges-view--challenges > div:nth-child(${i})`;
                const challenge = await waitForElement(challengeSelector, 2000);

                if (!challenge) {
                    // console.log(`[Runnan] Challenge ${i} not found.`);
                    continue;
                }

                simulateClick(challenge);
                await sleep(1000);

                // Click "Start Challenge" (开始挑战)
                const startBtn = findButtonByText(['Starting Challenge', 'Start Challenge', '开始挑战', '前往挑战']);
                if (startBtn) {
                    console.log('[Runnan] Clicking Start Challenge...');
                    simulateClick(startBtn);
                    await sleep(3000);

                    // Run AutoAll only if started
                    const success = await sbcAutoAll(i, sbcId);
                    if (!success) {
                        console.log('[Runnan] AutoAll failed. Stopping ForeverRun.');
                        stopRequested = true;
                        // Alert user?
                        alert('Runnan: Stopped due to repeated submit failures.');
                        break;
                    }
                } else {
                    console.log('[Runnan] Start Challenge button not found. Skipping challenge...');
                }

                // Return to challenges page
                const listCheck = await waitForElement('div.ut-sbc-challenges-view--challenges', 3000);
                if (!listCheck) {
                    console.log('[Runnan] Not in challenge list. Clicking Back.');
                    const backBtn = document.querySelector('button.ut-navigation-button-control');
                    if (backBtn) simulateClick(backBtn);
                    await sleep(2000);
                }
            }

            await sleep(1000);
        }
        console.log('[Runnan] ForeverRun Stopped');
    }

    // UI Initialization
    function initUI() {
        const container = document.createElement('div');
        Object.assign(container.style, {
            position: 'fixed',
            bottom: '40px',
            right: '40px',
            display: 'flex',
            flexDirection: 'column',
            gap: '10px',
            zIndex: 9999
        });

        const createBtn = (text, color, onClick) => {
            const btn = document.createElement('button');
            btn.textContent = text;
            Object.assign(btn.style, {
                padding: '10px 20px',
                background: color,
                color: 'white',
                border: 'none',
                borderRadius: '6px',
                cursor: 'pointer',
                fontWeight: 'bold',
                transition: 'transform 0.1s, opacity 0.2s',
                boxShadow: '0 2px 4px rgba(0,0,0,0.2)'
            });

            // Click animation (Scale)
            btn.addEventListener('mousedown', () => btn.style.transform = 'scale(0.95)');
            btn.addEventListener('mouseup', () => btn.style.transform = 'scale(1)');
            btn.addEventListener('mouseleave', () => btn.style.transform = 'scale(1)');

            // Handle click with debounce/disable
            btn.addEventListener('click', async (e) => {
                if (btn.disabled) return;

                // Visual feedback for click
                btn.style.opacity = '0.7';
                btn.style.transform = 'scale(0.95)';
                setTimeout(() => btn.style.transform = 'scale(1)', 100);

                // Disable briefly to prevent double click
                btn.disabled = true;
                const originalText = btn.textContent;
                btn.textContent = '...';

                try {
                    await onClick(e);
                } finally {
                    // Re-enable after short delay or action done
                    setTimeout(() => {
                        btn.disabled = false;
                        btn.style.opacity = '1';
                        btn.textContent = originalText;
                    }, 500);
                }
            });

            return btn;
        };

        // AutoAll Button
        const btnAuto = createBtn('AutoALL', '#28a745', async () => {
            stopRequested = false;
            await sbcAutoAll(0, null);
        });

        // Forever 423 Button
        const btnForever423 = createBtn('Forever 423', '#007bff', async () => {
            stopRequested = false;
            await foreverRun('423');
        });

        // Forever 422 Button
        const btnForever422 = createBtn('Forever 422', '#17a2b8', async () => {
            stopRequested = false;
            await foreverRun('422');
        });

        // Stop Button
        const btnStop = createBtn('Stop', '#dc3545', async () => {
            stopRequested = true;
            console.log('[Runnan] Stop Requested');
        });

        container.appendChild(btnAuto);
        container.appendChild(btnForever423);
        container.appendChild(btnForever422);
        container.appendChild(btnStop);
        document.body.appendChild(container);
    }

    page.addEventListener('load', initUI);
    // Fallback if load already fired
    setTimeout(initUI, 2000);
})();