Chess.com Stockfish Bot

Chess.com Stockfish Bot with Enhanced Auto-Match using Stockfish Online API

当前为 2025-03-21 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

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

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Chess.com Stockfish Bot
// @namespace    BottleOrg Scripts
// @version      1.6.2.19
// @description  Chess.com Stockfish Bot with Enhanced Auto-Match using Stockfish Online API
// @author       [REDACTED]
// @license      Chess.com Bot/Cheat by [REDACTED]
// @match       https://www.chess.com/play/*
// @match       https://www.chess.com/game/*
// @match       https://www.chess.com/puzzles/*
// @icon         data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @grant       GM_getValue
// @grant       GM_setValue
// @grant       GM_xmlhttpRequest
// @grant       GM_getResourceText
// @grant       GM_registerMenuCommand
// @require     https://greasyfork.org/scripts/445697/code/index.js
// @require     https://code.jquery.com/jquery-3.6.0.min.js
// @run-at      document-start
// ==/UserScript==

const currentVersion = '1.6.2.19';
const scriptURL = 'https://greasyfork.org/en/scripts/526240-chess-com-stockfish-bot';
const stockfishAPI_URI = "https://stockfish.online/api/s/v2.php";

// Function to check if the Stockfish API domain is accessible
function checkStockfishDomainAccess(callback) {
    console.log("Checking if Stockfish API domain is accessible...");
    const testFen = "rnbqkbnr/pppppppp/5n2/8/8/5N2/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
    const testDepth = 1;
    const testUrl = `${stockfishAPI_URI}?fen=${encodeURIComponent(testFen)}&depth=${testDepth}`;

    GM_xmlhttpRequest({
        method: "GET",
        url: testUrl,
        onload: function(response) {
            if (response.status === 200) {
                console.log("Stockfish API domain is accessible.");
                callback(true);
            } else {
                console.error("Stockfish API request failed with status:", response.status);
                callback(false);
            }
        },
        onerror: function(error) {
            console.error("Stockfish API request blocked or failed:", error);
            // Check if the error indicates the request was blocked (e.g., by an ad-blocker)
            if (error && error.statusText && error.statusText.includes("net::ERR_BLOCKED_BY_CLIENT")) {
                alert("Allow domain request to stockfish.online for the script to be able to work!");
            } else {
                alert("Failed to connect to Stockfish API. Please check your internet connection or allow requests to stockfish.online.");
            }
            callback(false);
        },
        timeout: 5000 // Set a timeout of 5 seconds for the test request
    });
}

function checkForUpdate() {
    console.log("Checking for script updates...");
    GM_xmlhttpRequest({
        method: "GET",
        url: scriptURL,
        onload: function(response) {
            if (response.status === 200) {
                const html = response.responseText;
                const versionMatch = html.match(/@version\s+([\d.]+)/);
                if (versionMatch && versionMatch[1]) {
                    const latestVersion = versionMatch[1];
                    console.log("Latest version found:", latestVersion);
                    if (compareVersions(latestVersion, currentVersion) > 0) {
                        const message = `New Version: ${latestVersion} has been uploaded. Would you like me to take you there or continue with old version ${currentVersion}? (Not recommended for stability)`;
                        if (confirm(message)) {
                            window.location.href = scriptURL;
                        } else {
                            console.log("User chose to continue with old version.");
                        }
                    } else {
                        console.log("No newer version available.");
                    }
                } else {
                    console.error("Could not find version in Greasy Fork page.");
                }
            } else {
                console.error("Failed to fetch script page:", response.status);
            }
        },
        onerror: function(error) {
            console.error("Error checking for update:", error);
        }
    });
}

function compareVersions(v1, v2) {
    const parts1 = v1.split('.').map(Number);
    const parts2 = v2.split('.').map(Number);
    for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
        const p1 = parts1[i] || 0;
        const p2 = parts2[i] || 0;
        if (p1 > p2) return 1;
        if (p1 < p2) return -1;
    }
    return 0;
}

function main() {
    var myVars = document.myVars = { autoMove: false, autoRun: false, autoMatch: false, delay: 0.1, hasAutoMatched: false, gameEnded: false, stockfishAccessible: false };
    var myFunctions = document.myFunctions = {};
    var currentStockfishVersion = "Stockfish API";
    var uiElementsLoaded = false;

    // Check Stockfish API access before proceeding
    checkStockfishDomainAccess(function(isAccessible) {
        myVars.stockfishAccessible = isAccessible;
        if (!isAccessible) {
            console.error("Stockfish API is not accessible. Script functionality will be limited.");
            return; // Stop script execution if Stockfish API is not accessible
        }
        proceedWithMainLogic();
    });

    function proceedWithMainLogic() {
        var stop_b = 0, stop_w = 0, s_br = 0, s_br2 = 0, s_wr = 0, s_wr2 = 0;

        myFunctions.rescan = function() {
            console.log("Rescanning board...");
            var boardElement = document.querySelector('chess-board, wc-chess-board');
            if (!boardElement) {
                console.warn("No board element found. Using default FEN.");
                return "rnbqkbnr/pppppppp/5n2/8/8/5N2/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
            }
            var pieces = $(boardElement).find(".piece").map(function() { return this.className; }).get();
            if (!pieces.length) {
                console.warn("No pieces found. Using default FEN.");
                return "rnbqkbnr/pppppppp/5n2/8/8/5N2/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
            }
            var boardArray = Array(64).fill('');
            pieces.forEach(piece => {
                var classes = piece.split(' ');
                var squareClass = classes.find(c => c.startsWith('square-'));
                var pieceClass = classes.find(c => /^[wb][prnbqk]$/.test(c));
                if (squareClass && pieceClass) {
                    var squareNum = squareClass.replace('square-', '');
                    var file = parseInt(squareNum[0]) - 1;
                    var rank = parseInt(squareNum[1]) - 1;
                    var square = (7 - rank) * 8 + file;
                    if (square >= 0 && square < 64) {
                        var pieceChar = {'wp': 'P', 'bp': 'p', 'wr': 'R', 'br': 'r', 'wn': 'N', 'bn': 'n',
                                        'wb': 'B', 'bb': 'b', 'wq': 'Q', 'bq': 'q', 'wk': 'K', 'bk': 'k'}[pieceClass];
                        boardArray[square] = pieceChar;
                    }
                }
            });
            var fen = '';
            for (var i = 0; i < 64; i++) {
                if (i % 8 === 0 && i > 0) fen += '/';
                var piece = boardArray[i];
                if (!piece) {
                    var emptyCount = 1;
                    while (i + 1 < 64 && !boardArray[i + 1] && (i + 1) % 8 !== 0) {
                        emptyCount++;
                        i++;
                    }
                    fen += emptyCount;
                } else {
                    fen += piece;
                }
            }
            var turn = $('.coordinates').children().first().text() === "1" ? 'b' : 'w';
            var castling = (stop_w ? '' : 'KQ') + (stop_b ? '' : 'kq') || '-';
            fen += ` ${turn} ${castling} - 0 1`;
            console.log("Generated FEN:", fen);
            return fen;
        };

        myFunctions.color = function(dat) {
            console.log("myFunctions.color CALLED with:", dat);
            const bestmoveUCI = dat.split(' ')[1];
            console.log("Extracted bestmove UCI:", bestmoveUCI);
            if (myVars.autoMove) myFunctions.movePiece(bestmoveUCI);
            else myFunctions.highlightMove(bestmoveUCI);
            isThinking = false;
            myFunctions.spinner();
        };

        myFunctions.highlightMove = function(bestmoveUCI) {
            var res1 = bestmoveUCI.substring(0, 2), res2 = bestmoveUCI.substring(2, 4);
            $(board).prepend(`<div class="highlight square-${res2}" style="background-color: rgb(235, 97, 80); opacity: 0.71;"></div>`)
                .children(':first').delay(1800).queue(function() { $(this).remove(); });
            $(board).prepend(`<div class="highlight square-${res1}" style="background-color: rgb(235, 97, 80); opacity: 0.71;"></div>`)
                .children(':first').delay(1800).queue(function() { $(this).remove(); });
            console.log("Highlighted:", bestmoveUCI);
        };

        myFunctions.movePiece = function(bestmoveUCI) {
            console.log("movePiece CALLED with:", bestmoveUCI);
            if (!board || !board.game) {
                console.error("Board or board.game not initialized!");
                return;
            }
            const fromSquare = bestmoveUCI.substring(0, 2);
            const toSquare = bestmoveUCI.substring(2, 4);
            const legalMoves = board.game.getLegalMoves();
            console.log("Legal moves:", legalMoves);
            let foundMove = legalMoves.find(move => move.from === fromSquare && move.to === toSquare);
            if (foundMove) {
                console.log("Executing move:", foundMove);
                board.game.move({ ...foundMove, promotion: 'q', animate: true, userGenerated: true });
                console.log("Move executed:", bestmoveUCI);
            } else {
                console.warn("No legal move found for:", bestmoveUCI);
            }
        };

        myFunctions.reloadChessEngine = function() { console.log("Reload not needed for API."); };
        myFunctions.loadChessEngine = function() {
            console.log("Using Stockfish API.");
            if (uiElementsLoaded) $('#engineVersionText')[0].innerHTML = "Engine: <strong>Stockfish API</strong>";
        };

        myFunctions.fetchBestMoveFromAPI = function(fen, depth) {
            if (!myVars.stockfishAccessible) {
                console.error("Stockfish API is not accessible. Cannot fetch best move.");
                isThinking = false;
                myFunctions.spinner();
                return;
            }
            const apiURL = `${stockfishAPI_URI}?fen=${encodeURIComponent(fen)}&depth=${depth}`;
            console.log(`Fetching from: ${apiURL}`);
            GM_xmlhttpRequest({
                method: "GET",
                url: apiURL,
                onload: function(response) {
                    if (response.status === 200) {
                        try {
                            const jsonResponse = JSON.parse(response.responseText);
                            if (jsonResponse.success) {
                                console.log("API Response:", jsonResponse);
                                myFunctions.color(jsonResponse.bestmove);
                            } else {
                                console.error("API failed:", jsonResponse);
                                isThinking = false;
                                myFunctions.spinner();
                            }
                        } catch (e) {
                            console.error("API parse error:", e);
                            isThinking = false;
                            myFunctions.spinner();
                        }
                    } else {
                        console.error("API error:", response.status);
                        isThinking = false;
                        myFunctions.spinner();
                    }
                },
                onerror: function(error) {
                    console.error("API request error:", error);
                    isThinking = false;
                    myFunctions.spinner();
                }
            });
        };

        myFunctions.startNewGame = function() {
            console.log("Starting new game...");
            const modalNewGameButton = $('.game-over-modal-content .game-over-buttons-component .cc-button-component:not([aria-label="Rematch"])');
            if (modalNewGameButton.length) {
                modalNewGameButton[0].click();
                console.log("Clicked New <x> min button from game-over modal.");
                myVars.hasAutoMatched = true;
                return;
            }

            const newGameButton = $('.game-over-buttons-component .cc-button-component:not([aria-label="Rematch"])');
            if (newGameButton.length) {
                newGameButton[0].click();
                console.log("Clicked New <x> min button from game-over.");
                myVars.hasAutoMatched = true;
                return;
            }

            const newGameTab = $('[data-tab="newGame"]');
            if (newGameTab.length) {
                newGameTab[0].click();
                console.log("Clicked New Game tab.");
                setTimeout(() => {
                    const playButton = $('.cc-button-component.cc-button-primary.cc-button-xx-large.cc-button-full');
                    if (playButton.length) {
                        playButton[0].click();
                        console.log("Clicked Play button.");
                        myVars.hasAutoMatched = true;
                        // Wait for the modal to appear and then attempt to click the guest button
                        setTimeout(() => {
                            attemptGuestButtonClick();
                        }, 2000); // Increased delay to ensure modal loads
                    } else {
                        console.error("Play button not found!");
                    }
                }, 500);
            } else {
                console.error("New Game tab not found!");
            }
        };

        myFunctions.declineRematch = function() {
            const declineButton = $('.cc-button-component.cc-button-secondary[aria-label="Decline"], .cc-button-component.cc-button-secondary:contains("Decline")');
            if (declineButton.length) {
                declineButton[0].click();
                console.log("Declined rematch.");
                return true;
            } else {
                console.log("No rematch decline button found.");
                return false;
            }
        };

        var lastValue = 11, MAX_DEPTH = 15, MIN_DEPTH = 1;

        myFunctions.runChessEngine = function(depth) {
            if (!myVars.stockfishAccessible) {
                console.error("Stockfish API is not accessible. Cannot run chess engine.");
                return;
            }
            depth = Math.max(MIN_DEPTH, Math.min(MAX_DEPTH, depth));
            var fen = myFunctions.rescan();
            console.log(`Analyzing FEN: ${fen}, Depth: ${depth}`);
            isThinking = true;
            myFunctions.spinner();
            myFunctions.fetchBestMoveFromAPI(fen, depth);
            lastValue = depth;
            updateDepthDisplay();
        };

        function updateDepthDisplay() {
            if (uiElementsLoaded && $('#depthText')[0]) $('#depthText')[0].innerHTML = `Depth: <strong>${lastValue}</strong>`;
        }

        myFunctions.incrementDepth = function(delta) {
            lastValue = Math.max(MIN_DEPTH, Math.min(MAX_DEPTH, lastValue + delta));
            updateDepthDisplay();
        };

        myFunctions.autoRun = function() {
            if (board && board.game && board.game.getTurn() === board.game.getPlayingAs()) {
                myFunctions.runChessEngine(lastValue);
            }
        };

        document.onkeydown = function(e) {
            switch (e.keyCode) {
                case 81: myFunctions.runChessEngine(1); break;  // Q
                case 87: myFunctions.runChessEngine(2); break;  // W
                case 69: myFunctions.runChessEngine(3); break;  // E
                case 82: myFunctions.runChessEngine(4); break;  // R
                case 84: myFunctions.runChessEngine(5); break;  // T
                case 89: myFunctions.runChessEngine(6); break;  // Y
                case 85: myFunctions.runChessEngine(7); break;  // U
                case 73: myFunctions.runChessEngine(8); break;  // I
                case 79: myFunctions.runChessEngine(9); break;  // O
                case 80: myFunctions.runChessEngine(10); break; // P
                case 65: myFunctions.runChessEngine(11); break; // A
                case 83: myFunctions.runChessEngine(12); break; // S
                case 68: myFunctions.runChessEngine(13); break; // D
                case 70: myFunctions.runChessEngine(14); break; // F
                case 71: myFunctions.runChessEngine(15); break; // G
                case 187: myFunctions.incrementDepth(1); break; // +
                case 189: myFunctions.incrementDepth(-1); break;// -
            }
        };

        myFunctions.spinner = function() {
            if (uiElementsLoaded && $('#overlay')[0]) {
                $('#overlay')[0].style.display = isThinking ? 'block' : 'none';
            }
        };

        let dynamicStyles = null;
        function addAnimation(body) {
            if (!dynamicStyles) {
                dynamicStyles = document.createElement('style');
                document.head.appendChild(dynamicStyles);
            }
            dynamicStyles.sheet.insertRule(body, dynamicStyles.length);
        }

        var loaded = false;
        myFunctions.loadEx = function() {
            try {
                console.log("Attempting to load UI...");
                board = document.querySelector('chess-board, wc-chess-board');
                var div = document.createElement('div');
                div.innerHTML = `
                    <div style="margin: 8px; padding: 10px; background: white; border: 1px solid #000; border-radius: 5px;">
                        <p id="depthText">Depth: <strong>${lastValue}</strong></p>
                        <button id="depthMinus">-</button>
                        <button id="depthPlus">+</button>
                        <p style="font-size: 12px;">Keys: Q-G (1-15), +/-</p>
                        <p id="engineVersionText">Engine: <strong>Stockfish API</strong></p>
                        <label><input type="checkbox" id="autoRun"> Auto Run</label><br>
                        <label><input type="checkbox" id="autoMove"> Auto Move</label><br>
                        <label><input type="checkbox" id="autoMatch"> Auto-Match</label><br>
                        <label>Min Delay (s): <input type="number" id="timeDelayMin" min="0.1" value="0.1" step="0.1" style="width: 60px;"></label><br>
                        <label>Max Delay (s): <input type="number" id="timeDelayMax" min="0.1" value="1" step="0.1" style="width: 60px;"></label><br>
                        <p style="color: blue; font-size: 12px; margin-top: 5px;">Script is made for guest account since Stockfish makes unhuman moves!</p>
                    </div>`;
                div.style.cssText = 'position: fixed; top: 10px; right: 10px; z-index: 10000;';
                document.body.appendChild(div);

                setTimeout(() => {
                    $('#depthPlus').off('click').on('click', () => myFunctions.incrementDepth(1));
                    $('#depthMinus').off('click').on('click', () => myFunctions.incrementDepth(-1));
                    $('#autoMatch').on('change', () => {
                        myVars.autoMatch = $('#autoMatch')[0].checked;
                        if (myVars.autoMatch && !myVars.hasAutoMatched) {
                            myFunctions.startNewGame();
                        }
                    });
                    console.log("Event listeners bound.");
                }, 100);

                var spinCont = document.createElement('div');
                spinCont.id = 'overlay';
                spinCont.style.cssText = 'display: none; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);';
                div.appendChild(spinCont);
                var spinr = document.createElement('div');
                spinr.style.cssText = "height: 64px; width: 64px; animation: rotate 0.8s infinite linear; border: 5px solid firebrick; border-right-color: transparent; border-radius: 50%;";
                spinCont.appendChild(spinr);
                addAnimation(`@keyframes rotate { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }`);

                loaded = true;
                uiElementsLoaded = true;
                console.log("UI loaded successfully.");
                myFunctions.loadChessEngine();
                checkForUpdate();
            } catch (error) {
                console.error("loadEx error:", error);
            }
        };

        function other(delay) {
            var endTime = Date.now() + delay;
            var timer = setInterval(() => {
                if (Date.now() >= endTime) {
                    myFunctions.autoRun();
                    canGo = true;
                    clearInterval(timer);
                }
            }, 10);
        }

        // Function to attempt clicking the guest button with retries
        function attemptGuestButtonClick(attempts = 10, delay = 1000) {
            if (attempts <= 0) {
                console.error("Failed to click 'Play as a Guest' button after all retries.");
                return;
            }

            // Check if the modal is present
            const modal = $('.authentication-modal-content');
            if (!modal.length) {
                console.log("Modal not found, attempts left:", attempts);
                setTimeout(() => attemptGuestButtonClick(attempts - 1, delay), delay);
                return;
            }

            const guestButton = $('#guest-button.authentication-intro-guest');
            if (guestButton.length && !myVars.hasAutoMatched) {
                console.log("Found 'Play as a Guest' button:", guestButton[0]);
                guestButton[0].click(); // Use jQuery's click method
                console.log("Clicked 'Play as a Guest' button.");
                setTimeout(() => {
                    const playButton = $('.cc-button-component.cc-button-primary.cc-button-xx-large.cc-button-full');
                    if (playButton.length) {
                        playButton[0].click();
                        console.log("Clicked Play button after guest prompt.");
                        myVars.hasAutoMatched = true;
                    } else {
                        console.error("Play button not found after guest prompt!");
                    }
                }, 1500); // Increased delay to 1500ms to ensure page transition
            } else {
                console.log("Guest button not found or already clicked, attempts left:", attempts);
                setTimeout(() => attemptGuestButtonClick(attempts - 1, delay), delay);
            }
        }

        const waitForChessBoard = setInterval(() => {
            if (!loaded) {
                myFunctions.loadEx();
            } else {
                clearInterval(waitForChessBoard);
                if (!board) board = document.querySelector('chess-board, wc-chess-board');
                myVars.autoRun = $('#autoRun')[0].checked;
                myVars.autoMove = $('#autoMove')[0].checked;
                myVars.autoMatch = $('#autoMatch')[0].checked;
                let minDel = parseFloat($('#timeDelayMin')[0].value) || 0.1;
                let maxDel = parseFloat($('#timeDelayMax')[0].value) || 1;
                myVars.delay = Math.random() * (maxDel - minDel) + minDel;
                myFunctions.spinner();
                myTurn = board && board.game && board.game.getTurn() === board.game.getPlayingAs();
                updateDepthDisplay();

                const gameOver = document.querySelector('.game-over-message-component') || document.querySelector('.game-result');
                if (gameOver && !myVars.gameEnded) {
                    console.log("Game ended detected (chat or result).");
                    myVars.gameEnded = true;
                    if (myVars.autoMatch) {
                        myFunctions.declineRematch();
                        setTimeout(() => {
                            myVars.hasAutoMatched = false;
                            myFunctions.startNewGame();
                        }, 1000);
                    }
                } else if (!gameOver && myVars.gameEnded) {
                    myVars.gameEnded = false;
                }

                const gameOverModal = $('.game-over-modal-content');
                if (myVars.autoMatch && gameOverModal.length && !myVars.hasAutoMatched) {
                    console.log("Game over modal detected.");
                    const newGameButton = gameOverModal.find('.game-over-buttons-component .cc-button-component:not([aria-label="Rematch"])');
                    if (newGameButton.length) {
                        newGameButton[0].click();
                        console.log("Clicked New <x> min button from game-over modal in interval.");
                        myVars.hasAutoMatched = true;
                    } else {
                        console.error("New <x> min button not found in game-over modal!");
                    }
                }

                if (myVars.autoRun && canGo && !isThinking && myTurn) {
                    canGo = false;
                    other(myVars.delay * 1000);
                }
            }
        }, 500);

        setTimeout(() => {
            if (!loaded) myFunctions.loadEx();
        }, 2000);
    }
}

var isThinking = false, canGo = true, myTurn = false, board;

window.addEventListener("load", () => main());