Sleazy Fork is available in English.
Reads FEN directly from Chess.com's API. Works on Canvas-rendered boards.
// ==UserScript==
// @name Chess.com Advisor (API Only)
// @namespace https://sleazyfork.org/en/users/123456
// @version 1.0
// @description Reads FEN directly from Chess.com's API. Works on Canvas-rendered boards.
// @author Potato
// @license MIT
// @match https://www.chess.com/*
// @match https://chess.com/*
// @run-at document-start
// @require https://cdnjs.cloudflare.com/ajax/libs/chess.js/0.10.3/chess.min.js
// @grant none
// ==/UserScript==
(function() {
'use strict';
// ─── PIECE VALUES ──────────────────────────────────────────────────────
const PV = {p:100,n:320,b:330,r:500,q:900,k:20000};
const PST = {
p:[0,0,0,0,0,0,0,0,50,50,50,50,50,50,50,50,10,10,20,30,30,20,10,10,5,5,10,25,25,10,5,5,0,0,0,20,20,0,0,0,5,-5,-10,0,0,-10,-5,5,5,10,10,-20,-20,10,10,5,0,0,0,0,0,0,0,0],
n:[-50,-40,-30,-30,-30,-30,-40,-50,-40,-20,0,0,0,0,-20,-40,-30,0,10,15,15,10,0,-30,-30,5,15,20,20,15,5,-30,-30,0,15,20,20,15,0,-30,-30,5,10,15,15,10,5,-30,-40,-20,0,5,5,0,-20,-40,-50,-40,-30,-30,-30,-30,-40,-50],
b:[-20,-10,-10,-10,-10,-10,-10,-20,-10,0,0,0,0,0,0,-10,-10,0,5,10,10,5,0,-10,-10,5,5,10,10,5,5,-10,-10,0,10,10,10,10,0,-10,-10,10,10,10,10,10,10,-10,-10,5,0,0,0,0,5,-10,-20,-10,-10,-10,-10,-10,-10,-20],
r:[0,0,0,0,0,0,0,0,5,10,10,10,10,10,10,5,-5,0,0,0,0,0,0,-5,-5,0,0,0,0,0,0,-5,-5,0,0,0,0,0,0,-5,-5,0,0,0,0,0,0,-5,-5,0,0,0,0,0,0,-5,0,0,0,5,5,0,0,0],
q:[-20,-10,-10,-5,-5,-10,-10,-20,-10,0,0,0,0,0,0,-10,-10,0,5,5,5,5,0,-10,-5,0,5,5,5,5,0,-5,0,0,5,5,5,5,0,-5,-10,5,5,5,5,5,0,-10,-10,0,5,0,0,0,0,-10,-20,-10,-10,-5,-5,-10,-10,-20],
k:[-30,-40,-40,-50,-50,-40,-40,-30,-30,-40,-40,-50,-50,-40,-40,-30,-30,-40,-40,-50,-50,-40,-40,-30,-30,-40,-40,-50,-50,-40,-40,-30,-20,-30,-30,-40,-40,-30,-30,-20,-10,-20,-20,-20,-20,-20,-20,-10,20,20,0,0,0,0,20,20,20,30,10,0,0,10,30,20]
};
// ─── UI ────────────────────────────────────────────────────────────────
function createUI() {
if (document.getElementById('pa-api-final')) return;
let d = document.createElement('div');
d.id = 'pa-api-final';
d.style.cssText = 'position:fixed;bottom:20px;right:20px;z-index:999999;background:#0d0d1a;color:#eee;font-family:sans-serif;font-size:14px;border-radius:12px;padding:16px;width:340px;border:1px solid #4a4a6a;box-shadow:0 8px 24px rgba(0,0,0,0.9);max-height:400px;overflow-y:auto;';
d.innerHTML = `
<div style="display:flex;justify-content:space-between;margin-bottom:6px;">
<strong style="color:#9b9bff;">🧠 Advisor (API)</strong>
<span id="ps-api-final" style="font-size:11px;color:#aaa;">⏳ Starting...</span>
</div>
<div id="pm-api-final" style="font-size:13px;color:#888;line-height:1.5;">
🔍 Waiting for API data...
</div>
<div id="pdebug-api-final" style="margin-top:6px;font-size:10px;color:#555;border-top:1px solid #333;padding-top:6px;word-break:break-all;">
⏳ Capturing game state...
</div>
`;
document.body.appendChild(d);
}
function setStatus(msg) {
let el = document.getElementById('ps-api-final');
if (el) el.textContent = msg;
}
function setDebug(msg) {
let el = document.getElementById('pdebug-api-final');
if (el) el.textContent = msg;
}
function showMoves(moves, info) {
let c = document.getElementById('pm-api-final');
if (!c) return;
if (!moves || !moves.length) {
c.innerHTML = `<div style="color:#ff6b6b;">❌ No moves</div><div style="font-size:10px;color:#666;margin-top:4px;">${info || ''}</div>`;
return;
}
let h = '<div style="color:#9b9bff;margin-bottom:4px;">🏆 Top 3 moves:</div>';
moves.forEach((m,i) => {
let col = i===0?'#7dff7d':i===1?'#ffd966':'#ff8a8a';
let san = m.san || m.from + m.to;
h += `<div data-from="${m.from}" data-to="${m.to}" data-prom="${m.promotion||''}" style="background:#2a2a44;padding:6px 10px;margin:3px 0;border-radius:6px;cursor:pointer;border-left:3px solid ${col};display:flex;justify-content:space-between;font-size:13px;"><span><strong>${i+1}.</strong> ${san}</span><span style="color:${col};">▶</span></div>`;
});
c.innerHTML = h;
c.querySelectorAll('[data-from]').forEach(el => {
el.onclick = function() {
let mv = { from: this.dataset.from, to: this.dataset.to, promotion: this.dataset.prom || undefined };
playMove(mv);
};
});
if (info) setDebug(info);
}
// ─── EVAL ──────────────────────────────────────────────────────────────
function evalGame(g) {
if (g.in_checkmate()) return g.turn()==='w'?-30000:30000;
if (g.in_draw()) return 0;
let s=0;
for(let r=0;r<8;r++) for(let f=0;f<8;f++) {
let p=g.board()[r][f];
if(!p) continue;
let i=r*8+f, v=PV[p.type]||0, t=(PST[p.type]||[])[p.color==='w'?i:63-i]||0;
s += p.color==='w' ? v+t : -(v+t);
}
return s;
}
function minimax(g,d,a,b,max) {
if(d===0||g.game_over()) return evalGame(g);
let moves=g.moves(), best=max?-Infinity:Infinity;
for(let m of moves) {
g.move(m);
let v=minimax(g,d-1,a,b,!max);
g.undo();
if(max){if(v>best)best=v;a=Math.max(a,v);}
else{if(v<best)best=v;b=Math.min(b,v);}
if(b<=a) break;
}
return best;
}
function getTop(g,cnt=3) {
let moves=g.moves({verbose:true});
if(!moves.length) return [];
let max=g.turn()==='w';
let scored=moves.map(m=>{g.move(m);let v=minimax(g,2,-Infinity,Infinity,!max);g.undo();return{move:m,score:v};});
scored.sort((a,b)=>max?b.score-a.score:a.score-b.score);
return scored.slice(0,cnt).map(x=>x.move);
}
// ─── CLICK ─────────────────────────────────────────────────────────────
function fire(x,y) {
let el=document.elementFromPoint(x,y);
if(!el) return;
let o={bubbles:true,cancelable:true,clientX:x,clientY:y,button:0};
el.dispatchEvent(new MouseEvent('mousedown',o));
el.dispatchEvent(new MouseEvent('mouseup',o));
el.dispatchEvent(new MouseEvent('click',o));
}
function playMove(mv) {
// Try to find the square by data attribute
let fromEl = document.querySelector(`[data-square="${mv.from}"]`);
if (!fromEl) {
// Fallback: try to find by piece class
const file = mv.from[0];
const rank = mv.from[1];
fromEl = document.querySelector(`.piece.square-${file.charCodeAt(0)-96}${rank}`);
}
if (!fromEl) return;
let r = fromEl.getBoundingClientRect();
fire(r.left + r.width/2, r.top + r.height/2);
setTimeout(() => {
let toEl = document.querySelector(`[data-square="${mv.to}"]`);
if (!toEl) {
const file = mv.to[0];
const rank = mv.to[1];
toEl = document.querySelector(`.piece.square-${file.charCodeAt(0)-96}${rank}`);
}
if (toEl) {
let r2 = toEl.getBoundingClientRect();
fire(r2.left + r2.width/2, r2.top + r2.height/2);
}
if (mv.promotion) {
setTimeout(() => {
let q = document.querySelector('.promotion-piece.wq,.promotion-piece.bq,[data-piece="q"]');
if (q) q.click();
}, 300);
}
}, 200);
}
// ─── API HOOK ──────────────────────────────────────────────────────────
let lastFen = '';
let scanCount = 0;
function processFEN(fen) {
if (!fen || fen === lastFen) return;
if (!fen.includes('/')) return;
lastFen = fen;
scanCount++;
setDebug(`📊 Scan ${scanCount}: New FEN captured`);
try {
let g = new Chess(fen);
if (g.game_over()) {
showMoves([], 'Game over');
setStatus('🏁 Game over');
return;
}
let turn = g.turn();
let myColor = 'w'; // Default to white
setDebug(`👤 Turn: ${turn} | My color: ${myColor}`);
if (turn !== myColor) {
showMoves([], `⏳ Opponent's turn (${turn})`);
setStatus('⏳ Waiting');
return;
}
setStatus('🤔 Analyzing...');
let top = getTop(g, 3);
if (top && top.length) {
showMoves(top, `✅ ${top.length} moves found`);
setStatus('✅ Ready');
} else {
showMoves([], `⚠️ No legal moves`);
setStatus('❌ No moves');
}
} catch(e) {
setDebug(`❌ Error: ${e.message}`);
setStatus('❌ Error');
}
}
// ─── HOOK FETCH/XHR ──────────────────────────────────────────────────
function hookNetwork() {
const origFetch = window.fetch;
window.fetch = function(...args) {
const url = args[0];
if (typeof url === 'string' && url.includes('/callback/game/')) {
return origFetch.apply(this, args).then(res => {
const cloned = res.clone();
cloned.json().then(data => {
if (data && data.game && data.game.fen) {
processFEN(data.game.fen);
}
}).catch(() => {});
return res;
});
}
return origFetch.apply(this, args);
};
const origXHR = window.XMLHttpRequest;
window.XMLHttpRequest = function() {
const xhr = new origXHR();
const origOpen = xhr.open;
const origSend = xhr.send;
xhr.open = function(method, url, ...rest) {
this._url = url;
return origOpen.apply(this, [method, url, ...rest]);
};
xhr.send = function(...args) {
this.addEventListener('readystatechange', function() {
if (this.readyState === 4 && this.status === 200) {
if (this._url && this._url.includes('/callback/game/')) {
try {
const data = JSON.parse(this.responseText);
if (data && data.game && data.game.fen) {
processFEN(data.game.fen);
}
} catch(e) {}
}
}
});
return origSend.apply(this, args);
};
return xhr;
};
setDebug('✅ Network hooks installed');
}
// ─── START ─────────────────────────────────────────────────────────────
createUI();
setStatus('⏳ Installing hooks...');
hookNetwork();
setStatus('⏳ Waiting for game data...');
// Try to find FEN in page source as fallback
setTimeout(() => {
const scripts = document.querySelectorAll('script');
for (const script of scripts) {
const content = script.textContent || '';
const fenMatch = content.match(/["']fen["']\s*:\s*["']([^"']+)["']/);
if (fenMatch) {
processFEN(fenMatch[1]);
break;
}
}
}, 3000);
})();