NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript== // @name Lichess - Detailed Moves // @license GPL-3.0-only // @namespace https://github.com/sealldeveloper/lichess-better-moves // @contributionURL https://github.com/sealldeveloper/lichess-better-moves // @version 0.5 // @description Show brillant, excellent, great and book moves on lichess.org as chess.com does, an updated version of Thomas Sihapanya's version. // @author Seall.DEV & Thomas Sihapnya // @require https://greasyfork.org/scripts/47911-font-awesome-all-js/code/Font-awesome%20AllJs.js?version=275337 // @include /^https\:\/\/lichess\.org\/[a-zA-Z0-9]{8,}/ // @grant GM.xmlHttpRequest // @grant unsafeWindow // @inject-into content // ==/UserScript== // ==OpenUserJS== // @author sealldeveloper // ==/OpenUserJS== (function() { 'use strict'; const GOOD_MOVE_THRESOLD = 0.6; const EXCELLENT_MOVE_THRESOLD = 1; const BRILLANT_MOVE_THRESOLD = 2; const CHECKMATE_IN_X_MOVES_VALUE = 100; let goodMoves = { white : { 'book' : 0, 'good' : 0, 'excellent': 0, 'brillant': 0, }, black : { 'book' : 0, 'good' : 0, 'excellent': 0, 'brillant': 0, } } /** * Wait for the page to load all its elements before running the script */ window.addEventListener('load', function() { /** * Create click event since .click on elements doesn't work quite well in Chrome */ let clickEvent = document.createEvent('MouseEvents'); clickEvent.initMouseEvent('mousedown', true, true); /** * Moves explorer must be opened to detect book moves. If not, user is prompted to run an analysis and reload the page. */ const formAnalysis = document.getElementsByClassName('future-game-analysis')[0]; if (typeof(formAnalysis) !== 'undefined') { alert('Lichess Detailed Moves: Please run an analysis and reload the page when finished. You will then be prompted to accept cross-origin resource : you can safely allow it (it will fetch data for an opening API) !'); } /** * Check if users is in analysis page and analysis has been run */ if (document.getElementsByClassName('analyse__tools').length > 0 && document.getElementsByClassName('future-game-analysis').length === 0 && document.getElementsByClassName('computer-analysis active').length > 0) { const ecoCodesApiUrl = 'https://github.com/sealldeveloper/lichess-detailed-moves/raw/main/data/eco.json'; /** * Loads ECO codes API * https://github.com/sealldeveloper/lichess-detailed-moves/raw/main/data/eco.json */ function loadEcoCodesApi() { GM.xmlHttpRequest({ method: "GET", url:ecoCodesApiUrl, onload: function(response) { lichessGoodMoves(JSON.parse(response.responseText)); }, onerror: function(err) { alert('Lichess Detailed Moves: The script cannot be launched (maybe you have forbid the access to a cross-origin resource ?) - Refresh the page if you want to start again.'); } }); } function loadMoves(ecoCodes) { let domMoves = document.getElementsByTagName('move'); let moves = []; let previousEval = { value: '+0.0', symbol: '+', absVal: '0.0' }; Object.values(domMoves).forEach(domMove => { if (!domMove.classList.contains('empty')) { if ("undefined" !== typeof(domMove.childNodes)) { domMove.childNodes.forEach(node => { if ('SAN' === node.tagName) { /** * Handle opening */ moves.push(node.innerHTML); let currentColor = checkColor(moves.length-1); let currentPgn = createPgnMoves(moves); let foundOpening = ecoCodes.find(eco => eco.moves.toLowerCase().trim() == currentPgn.toLowerCase().trim()); if (typeof(foundOpening) !== 'undefined') { handleOpeningMove(node, foundOpening, currentColor); } /** * Handle evaluation */ if (!node.innerHTML.endsWith('#')){ let currentEval = { textValue: domMove.getElementsByTagName('eval')[0].innerHTML, symbol: domMove.getElementsByTagName('eval')[0].innerHTML.charAt(0) } if (currentEval.symbol == '#') { currentEval.value = currentColor == 'white' ? - CHECKMATE_IN_X_MOVES_VALUE : CHECKMATE_IN_X_MOVES_VALUE; } else { currentEval = { textValue: currentEval.textValue, symbol: currentEval.symbol, value: (currentEval.symbol == '+') ? parseFloat(currentEval.textValue.substring(1)) : 0 - parseFloat(currentEval.textValue.substring(1)) } } let delta = currentEval.value - previousEval.value; let moveText = node.innerHTML; if ("white" === currentColor) { if (delta >= GOOD_MOVE_THRESOLD && delta < EXCELLENT_MOVE_THRESOLD) { node.innerHTML = '<span style="color: #b2f196;">'+ moveText+'!?'+ '</span>'; goodMoves.white.good++; } if (delta >= EXCELLENT_MOVE_THRESOLD && delta < BRILLANT_MOVE_THRESOLD) { node.innerHTML = '<span style="color: #96bc4b;">'+ moveText+'!'+ '</span>'; goodMoves.white.excellent++; } if (delta >= BRILLANT_MOVE_THRESOLD) { node.innerHTML = '<span style="color: #1baca6;">'+ moveText+'!!'+ '</span>'; goodMoves.white.brillant++; } } if ("black" === currentColor) { if (delta <= -GOOD_MOVE_THRESOLD && delta > -EXCELLENT_MOVE_THRESOLD) { node.innerHTML = '<span style="color: #b2f196;">'+ moveText+'!?'+ '</span>'; goodMoves.black.good++; } if (delta <= -EXCELLENT_MOVE_THRESOLD && delta > -BRILLANT_MOVE_THRESOLD) { node.innerHTML = '<span style="color: #96bc4b;">'+ moveText+'!'+ '</span>'; goodMoves.black.excellent++; } if (delta <= -BRILLANT_MOVE_THRESOLD) { node.innerHTML = '<span style="color: #1baca6;">'+ moveText+'!!'+ '</span>'; goodMoves.black.brillant++; } } previousEval = currentEval; } } }) } } }); return moves; } function handleOpeningMove(node, opening, currentColor) { const moveText = node.innerHTML; node.innerHTML = '<span style="color: #a88865;">'+ moveText+ ' <i class="fas fa-book" style="font-size: 0.7em"></i>'+ '</span>'+ '<pre style="font-size:0.7em; width:0">'+opening.name+'</pre>'; node.parentElement.title = opening.name; goodMoves[currentColor].book++; } function checkColor(index) { if (0 === index%2) { return "white"; } if (0 !== index%2) { return "black"; } } function createPgnMoves(moves) { let pgn = ''; moves.forEach((move, index) => { if ("white" === checkColor(index)) { pgn += (index/2+1) + '. ' + move; } if ("black" === checkColor(index)) { pgn += ' ' + move + ' '; } }); return pgn; } function showDataInTable() { const whiteTable = document.getElementsByClassName('advice-summary__side')[0] const blackTable = document.getElementsByClassName('advice-summary__side')[1] function dataPoint(colour,symbol,data,text,table,coloured) { var first = true table.childNodes.forEach(node => { if (node.innerHTML.includes('inaccuracies') && first === true) { const before=node const div = document.createElement('div'); if (data !== 0){ div.style='color: '+coloured; } div.classList.add('symbol'); div.classList.add('advice-summary__error'); div.setAttribute('data-color', colour); div.setAttribute('data-symbol', symbol); const strong = document.createElement('strong') strong.innerHTML = data; div.append(strong) div.innerHTML = div.innerHTML+text; table.insertBefore(div,before); first = false } }) } // White dataPoint('white','!!',goodMoves.white.brillant,' brilliancies',whiteTable,'#1baca6') dataPoint('white','!',goodMoves.white.excellent,' excellencies',whiteTable,'#96bc4b') dataPoint('white','!?',goodMoves.white.good,' greats',whiteTable,'#b2f196') dataPoint('white','Book',goodMoves.white.book,' book',whiteTable,'#a88865') // Black dataPoint('black','!!',goodMoves.black.brillant,' brilliancies',blackTable,'#1baca6') dataPoint('black','!',goodMoves.black.excellent,' excellencies',blackTable,'#96bc4b') dataPoint('black','!?',goodMoves.black.good,' greats',blackTable,'#b2f196') dataPoint('black','Book',goodMoves.black.book,' book',blackTable,'#a88865') } function lichessGoodMoves(ecoCodes) { console.log('Lichess Detailed Moves: Successfully started!'); loadMoves(ecoCodes); showDataInTable(); } // Start the app ! loadEcoCodesApi(); } // check if users is in analysis page }, false); // addEventListener('load', callback) })(); // Immediately-Invoked Function Expression (function() {})())