-
-
Save nicolas-goudry/3912ca31f1d8f630a8af160eedbe4df6 to your computer and use it in GitHub Desktop.
// New version courtesy of JCluzet. Thanks! | |
(() => { | |
const STORAGE_KEY = 'lucca_faces_data_v2'; | |
let people = JSON.parse(localStorage.getItem(STORAGE_KEY)) || {}; | |
let currentImageHash = ''; | |
let retryAttempts = 0; | |
const MAX_RETRY_ATTEMPTS = 5; // Adjust max attempts count | |
function getImageHash(imageSrc) { | |
return new Promise((resolve, reject) => { | |
const img = new Image(); | |
img.crossOrigin = 'Anonymous'; | |
img.onload = () => { | |
const canvas = document.createElement('canvas'); | |
const ctx = canvas.getContext('2d'); | |
canvas.width = img.width; | |
canvas.height = img.height; | |
ctx.drawImage(img, 0, 0, img.width, img.height); | |
canvas.toBlob(blob => { | |
const reader = new FileReader(); | |
reader.onloadend = () => { | |
crypto.subtle.digest('SHA-256', reader.result).then(hashBuffer => { | |
const hashArray = Array.from(new Uint8Array(hashBuffer)); | |
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); | |
resolve(hashHex); | |
}); | |
}; | |
reader.onerror = reject; | |
reader.readAsArrayBuffer(blob); | |
}); | |
}; | |
img.onerror = () => { | |
reject('Image load error'); | |
}; | |
img.src = imageSrc; | |
}); | |
} | |
async function handleNewImage(imageSrc) { | |
try { | |
const hashHex = await getImageHash(imageSrc); | |
if (hashHex !== currentImageHash) { | |
currentImageHash = hashHex; | |
if (people[currentImageHash]) { | |
const choices = document.querySelectorAll('#game .answers .answer'); | |
const correctAnswer = [...choices].find(choice => choice.textContent.trim() === people[currentImageHash].trim()); | |
if (correctAnswer) { | |
correctAnswer.click(); | |
} | |
} else { | |
chooseRandomAnswer(); | |
} | |
} | |
retryAttempts = 0; // Reset attempts counter on success | |
} catch (error) { | |
console.error('Error processing image:', error); | |
retryAttempts++; | |
if (retryAttempts <= MAX_RETRY_ATTEMPTS) { | |
console.log(`Retrying (${retryAttempts}/${MAX_RETRY_ATTEMPTS})...`); | |
retryHandleImage(imageSrc); | |
} else { | |
console.log('Max retry attempts reached. Skipping image.'); | |
retryAttempts = 0; // Reset attempts counter if limit reached | |
clickGoButton(); // Click on "Go" button after reaching end of the game | |
} | |
} | |
} | |
function retryHandleImage(imageSrc) { | |
setTimeout(() => handleNewImage(imageSrc), 100); | |
} | |
function chooseRandomAnswer() { | |
const choices = document.querySelectorAll('#game .answers .answer'); | |
const randomChoice = choices[Math.floor(Math.random() * choices.length)]; | |
randomChoice.click(); | |
setTimeout(() => { | |
const correctAnswer = document.querySelector('#game .answers .is-right'); | |
if (correctAnswer) { | |
const name = correctAnswer.textContent.trim(); | |
people[currentImageHash] = name; | |
localStorage.setItem(STORAGE_KEY, JSON.stringify(people)); | |
console.log(`Discovered ${name} for hash ${currentImageHash}`); | |
} else { | |
console.error('Failed to discover the correct answer!'); | |
} | |
}, 500); // Wait a bit to allow correct answer to be shown | |
} | |
function startGame() { | |
const startButtonContainer = document.querySelector('.main-container.has-loaded .logo .rotation-loader'); | |
if (startButtonContainer) { | |
startButtonContainer.click(); | |
console.log('Clicked the start button'); | |
} else { | |
console.error('Start button not found. Make sure the game is loaded.'); | |
} | |
} | |
function restartGame() { | |
const replayButton = document.querySelector('.main-container .results-card button.button.mod-pill.palette-secondary.mod-XL'); | |
if (replayButton) { | |
replayButton.click(); | |
console.log('Clicked the replay button'); | |
} else { | |
console.log('Please click "Replay" to restart the game.'); | |
} | |
} | |
function clickGoButton() { | |
// Try to click on several elements until "Go" button is found | |
const elementsToClick = [ | |
document.querySelector('.main-container .logo .rotation-loader'), | |
document.querySelector('.main-container .results-card button.button.mod-pill.palette-secondary.mod-XL'), | |
document.querySelector('.main-container .results-card button.button.mod-pill.palette-primary.mod-L'), | |
document.querySelector('.main-container .results-card button.button.mod-pill.palette-primary.mod-XL'), | |
document.querySelector('.main-container .results-card button.button.mod-pill.palette-tertiary.mod-XL') | |
// Add other CSS selectors here if needed | |
]; | |
let clicked = false; | |
elementsToClick.forEach(element => { | |
if (element && !clicked) { | |
element.click(); | |
console.log('Clicked a button to restart the game.'); | |
clicked = true; | |
} | |
}); | |
if (!clicked) { | |
console.log('Go button not found. Please click "Replay" and "Go" manually to restart.'); | |
} | |
} | |
function observeGame() { | |
const gameElement = document.querySelector('#game'); | |
if (!gameElement) { | |
console.error('Game element not found. Make sure the game is loaded.'); | |
return; | |
} | |
const observer = new MutationObserver(() => { | |
const imageElement = document.querySelector('#game app-timer .image'); | |
if (imageElement) { | |
const imageSrc = imageElement.style.backgroundImage.match(/url\("(.*)"\)/)[1]; | |
handleNewImage(imageSrc); | |
} | |
const gameEnded = !!document.querySelector('.main-container .results-card'); | |
if (gameEnded) { | |
restartGame(); | |
startGame(); // Restart game after click on "Rejouer" and "Go" | |
} | |
}); | |
observer.observe(gameElement, { childList: true, subtree: true }); | |
} | |
function main() { | |
startGame(); | |
observeGame(); | |
console.log('Script loaded: It will automatically start the game and handle answers.'); | |
} | |
// Start main function right away | |
main(); | |
})(); |
@JCluzet Dude that’s neat! I can’t test it but I will trust that it works 🙂
I updated the gist with your code 😉
This is too slow !
You can use network request only with NodeJs for example 😇 😉
I try to stay under 5 seconds for all 10 questions.
by making more than 1550 or 1600 you will be banned for 1 month, you can try my new script :
(() => {
const STORAGE_KEY = 'lucca_faces_data_v2';
let people = JSON.parse(localStorage.getItem(STORAGE_KEY)) || {};
let currentImageHash = '';
let retryAttempts = 0;
const MAX_RETRY_ATTEMPTS = 5;
let totalPointsNeeded = 0;
let questionsAnswered = 0; // Compteur de questions répondues
let currentTotalScore = 0; // Score total actuel
// Demander le score cible à l'utilisateur
let targetScore = parseInt(prompt('Veuillez entrer le score cible (entre 500 et 1700) :'), 10);
if (isNaN(targetScore) || targetScore < 500 || targetScore > 1700) {
targetScore = 1300; // Valeur par défaut si l'entrée est invalide
}
// Ajuster le calcul du délai en fonction du score cible
const minScore = 500;
const maxScore = 1700;
const minTimePerQuestion = 3000; // Délai pour un score de 500
const maxTimePerQuestion = 0; // Délai pour un score de 1700
// Calculer le délai pour chaque question en fonction du score cible
const timePerQuestion = calculateTimePerQuestion(targetScore, minScore, maxScore, minTimePerQuestion, maxTimePerQuestion);
console.log(Time per question: ${timePerQuestion}ms for target score: ${targetScore}
);
function calculateTimePerQuestion(score, minScore, maxScore, minTime, maxTime) {
if (score <= minScore) {
return minTime;
}
if (score >= maxScore) {
return maxTime;
}
// Calculer le délai en utilisant une interpolation linéaire
const scoreRange = maxScore - minScore;
const timeRange = minTime - maxTime;
const timePerQuestion = maxTime + ((score - minScore) / scoreRange) * timeRange;
return Math.round(timePerQuestion); // Arrondir le délai
}
function getTotalPointsFromHeaderText(headerText) {
const regex = /(\d+) pts/;
const match = headerText.match(regex);
if (match && match.length > 1) {
return parseInt(match[1], 10);
}
return 0;
}
function getPointsFromSpanText(spanText) {
const regex = /+ (\d+) pts/;
const match = spanText.match(regex);
if (match && match.length > 1) {
return parseInt(match[1], 10);
}
return 0;
}
function getImageHash(imageSrc) {
return new Promise((resolve, reject) => {
const img = new Image();
img.crossOrigin = 'Anonymous';
img.onload = () => {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = img.width;
canvas.height = img.height;
ctx.drawImage(img, 0, 0, img.width, img.height);
canvas.toBlob(blob => {
const reader = new FileReader();
reader.onloadend = () => {
crypto.subtle.digest('SHA-256', reader.result).then(hashBuffer => {
const hashArray = Array.from(new Uint8Array(hashBuffer));
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
resolve(hashHex);
});
};
reader.onerror = reject;
reader.readAsArrayBuffer(blob);
});
};
img.onerror = () => {
reject('Image load error');
};
img.src = imageSrc;
});
}
function handleNewImage(imageSrc) {
getImageHash(imageSrc)
.then(hashHex => {
if (hashHex !== currentImageHash) {
currentImageHash = hashHex;
if (people[currentImageHash]) {
const choices = document.querySelectorAll('#game .answers .answer');
const correctAnswer = [...choices].find(choice => choice.textContent.trim() === people[currentImageHash].trim());
if (correctAnswer) {
setTimeout(() => {
correctAnswer.click();
// Polling pour observer les changements dans l'élément qui affiche le score gagné
pollForScoreElement((pointsEarned, newTotalScore) => {
questionsAnswered++;
currentTotalScore = newTotalScore;
// Estimer le nombre de questions restantes
const questionsRemaining = 10 - questionsAnswered;
// Ajuster le délai en fonction du nombre de questions restantes et du score nécessaire
const adjustedTimePerQuestion = calculateAdjustedTimePerQuestion(totalPointsNeeded, questionsRemaining);
const scoreNeededForNextQuestion = questionsRemaining > 0 ? Math.ceil((targetScore - currentTotalScore) / questionsRemaining) : 0;
console.log(`${questionsAnswered}/10: ${people[currentImageHash]} , score obtenu: ${pointsEarned} , score total: ${currentTotalScore} , score visé pour la prochaine question: ${scoreNeededForNextQuestion}`);
if (questionsAnswered < 10) {
setTimeout(() => {
handleNewImage(imageSrc);
}, adjustedTimePerQuestion);
} else {
console.log('Game completed. Total points achieved.');
}
});
}, timePerQuestion);
} else {
console.error('Correct answer element not found.');
chooseRandomAnswer();
}
} else {
chooseRandomAnswer();
console.log(`PERSONNE INCONNU : Enregistrement du hash ${currentImageHash} pour la personne ${imageSrc}`);
}
}
retryAttempts = 0;
})
.catch(error => {
console.error('Error processing image:', error);
retryAttempts++;
if (retryAttempts <= MAX_RETRY_ATTEMPTS) {
console.log(`Retrying (${retryAttempts}/${MAX_RETRY_ATTEMPTS})...`);
retryHandleImage(imageSrc);
} else {
console.log('Max retry attempts reached. Skipping image.');
retryAttempts = 0;
clickGoButton();
}
});
}
function pollForScoreElement(callback) {
const interval = setInterval(() => {
const imageOverlayElement = document.querySelector('.image-container .image-overlay span.score');
if (imageOverlayElement) {
clearInterval(interval);
const pointsEarned = getPointsFromSpanText(imageOverlayElement.textContent);
const currentTotalScore = getTotalPointsFromHeaderText(document.querySelector('.score-header .score').textContent);
callback(pointsEarned, currentTotalScore);
}
}, 100);
}
function calculateAdjustedTimePerQuestion(totalPointsNeeded, questionsRemaining) {
// Calculer un délai ajusté en fonction du nombre de points nécessaires et des questions restantes
// Par exemple, vous pouvez ralentir légèrement si le score nécessaire diminue
const baseTime = 3000; // Délai de base pour un score de 500
const targetTime = 0; // Délai cible pour un score de 1700
const timeRange = baseTime - targetTime;
const adjustedTime = targetTime + ((totalPointsNeeded / targetScore) * timeRange);
// Vous pouvez ajuster davantage en fonction du nombre de questions restantes
const adjustedTimePerQuestion = adjustedTime / questionsRemaining;
return Math.round(adjustedTimePerQuestion); // Arrondir le délai ajusté
}
function retryHandleImage(imageSrc) {
setTimeout(() => handleNewImage(imageSrc), 100);
}
function chooseRandomAnswer() {
const choices = document.querySelectorAll('#game .answers .answer');
if (choices.length > 0) {
const randomChoice = choices[Math.floor(Math.random() * choices.length)];
randomChoice.click();
setTimeout(() => {
const correctAnswer = document.querySelector('#game .answers .is-right');
if (correctAnswer) {
const name = correctAnswer.textContent.trim();
people[currentImageHash] = name;
localStorage.setItem(STORAGE_KEY, JSON.stringify(people));
console.log(Discovered ${name} for hash ${currentImageHash}
);
} else {
console.error('Failed to discover the correct answer!');
}
}, 500);
} else {
console.error('No choices available to click.');
}
}
function startGame() {
const startButtonContainer = document.querySelector('.main-container.has-loaded .logo .rotation-loader');
if (startButtonContainer) {
startButtonContainer.click();
console.log('Clicked the start button');
} else {
console.error('Start button not found. Make sure the game is loaded.');
}
}
function restartGame() {
const replayButton = document.querySelector('.main-container .results-card button.button.mod-pill.palette-secondary.mod-XL');
if (replayButton) {
replayButton.click();
console.log('Clicked the replay button');
} else {
console.log('Please click "Replay" to restart the game.');
}
const goButton = document.querySelector('.main-container .results-card button.button.mod-pill.palette-primary.mod-L, .main-container .results-card button.button.mod-pill.palette-secondary.mod-XL');
if (goButton) {
goButton.click();
console.log('Clicked the go button');
} else {
console.log('Please click "Go" to restart the game.');
}
}
function observeGame() {
const gameElement = document.querySelector('#game');
if (!gameElement) {
console.error('Game element not found. Make sure the game is loaded.');
return;
}
const observer = new MutationObserver(() => {
const imageElement = document.querySelector('#game app-timer .image');
if (imageElement) {
const imageSrc = imageElement.style.backgroundImage.match(/url("(.*)")/)[1];
handleNewImage(imageSrc);
}
const gameEnded = !!document.querySelector('.main-container .results-card');
if (gameEnded) {
const headerText = document.querySelector('.main-container .results-card .header').textContent;
const pointsEarned = getTotalPointsFromHeaderText(headerText);
totalPointsNeeded -= pointsEarned;
if (questionsAnswered < 10) {
restartGame();
startGame();
} else {
console.log('Game completed. Total points achieved.');
}
}
});
observer.observe(gameElement, { childList: true, subtree: true });
}
function main() {
startGame();
observeGame();
console.log('Script loaded: It will automatically start the game and handle answers.');
}
main();
})();
And the speed, you can disable throting in dev console inspect with Chrome, and disable cache, like this you can do the maximum score :)
hum 🤔
J'avais une version à exécuter comme ça dans le navigateur mais je trouvais ça trop lent.
Je fais des call en direct à l'API sans délais mais je n'arrive pas à atteindre les 1500.
I tweaked the code a bit so it could run before even pressing "Go."
So, it's very simple:
- Enter the code in the console
- Enter
- Press "Go"
;(() => {
const STORAGE_KEY = 'lucca_faces_data_v2'
let people = JSON.parse(localStorage.getItem(STORAGE_KEY)) || {}
let currentImageHash = ''
let retryAttempts = 0
const MAX_RETRY_ATTEMPTS = 5
let totalPointsNeeded = 0
let questionsAnswered = 0
let currentTotalScore = 0
const targetScore = (() => {
const input = prompt('Quel score cible veux-tu atteindre ? (entre 500 et 1700)', '1700')
const parsed = parseInt(input, 10)
return isNaN(parsed) || parsed < 500 || parsed > 1700 ? 1700 : parsed
})()
const minScore = 500
const maxScore = 1700
const minTimePerQuestion = 3000
const maxTimePerQuestion = 0
const timePerQuestion = calculateTimePerQuestion(
targetScore,
minScore,
maxScore,
minTimePerQuestion,
maxTimePerQuestion
)
console.log(`Time per question: ${timePerQuestion}ms for target score: ${targetScore}`)
function calculateTimePerQuestion(score, minScore, maxScore, minTime, maxTime) {
if (score <= minScore) return minTime
if (score >= maxScore) return maxTime
const scoreRange = maxScore - minScore
const timeRange = minTime - maxTime
const timePerQuestion = maxTime + ((score - minScore) / scoreRange) * timeRange
return Math.round(timePerQuestion)
}
function getTotalPointsFromHeaderText(headerText) {
const regex = /(\d+) pts/
const match = headerText.match(regex)
return match ? parseInt(match[1], 10) : 0
}
function getPointsFromSpanText(spanText) {
const regex = /\+ (\d+) pts/
const match = spanText.match(regex)
return match ? parseInt(match[1], 10) : 0
}
function getImageHash(imageSrc) {
return new Promise((resolve, reject) => {
const img = new Image()
img.crossOrigin = 'Anonymous'
img.onload = () => {
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
canvas.width = img.width
canvas.height = img.height
ctx.drawImage(img, 0, 0)
canvas.toBlob((blob) => {
const reader = new FileReader()
reader.onloadend = () => {
crypto.subtle.digest('SHA-256', reader.result).then((hashBuffer) => {
const hashArray = Array.from(new Uint8Array(hashBuffer))
const hashHex = hashArray.map((b) => b.toString(16).padStart(2, '0')).join('')
resolve(hashHex)
})
}
reader.onerror = reject
reader.readAsArrayBuffer(blob)
})
}
img.onerror = () => reject('Image load error')
img.src = imageSrc
})
}
function handleNewImage(imageSrc) {
getImageHash(imageSrc)
.then((hashHex) => {
if (hashHex !== currentImageHash) {
currentImageHash = hashHex
if (people[currentImageHash]) {
const choices = document.querySelectorAll('#game .answers .answer')
const correctAnswer = [...choices].find(
(choice) => choice.textContent.trim() === people[currentImageHash].trim()
)
if (correctAnswer) {
setTimeout(() => {
correctAnswer.click()
pollForScoreElement((pointsEarned, newTotalScore) => {
questionsAnswered++
currentTotalScore = newTotalScore
const questionsRemaining = 10 - questionsAnswered
const adjustedTimePerQuestion = calculateAdjustedTimePerQuestion(
targetScore - currentTotalScore,
questionsRemaining
)
const scoreNeededForNextQuestion =
questionsRemaining > 0
? Math.ceil((targetScore - currentTotalScore) / questionsRemaining)
: 0
console.log(
`${questionsAnswered}/10: ${people[currentImageHash]}, score obtenu: ${pointsEarned}, score total: ${currentTotalScore}, score visé pour la prochaine question: ${scoreNeededForNextQuestion}`
)
if (questionsAnswered < 10) {
setTimeout(() => {
handleNewImage(imageSrc)
}, adjustedTimePerQuestion)
} else {
console.log('Game completed. Total points achieved.')
}
})
}, timePerQuestion)
} else {
console.error('Correct answer element not found.')
chooseRandomAnswer()
}
} else {
chooseRandomAnswer()
console.log(
`PERSONNE INCONNUE : Enregistrement du hash ${currentImageHash} pour la personne ${imageSrc}`
)
}
}
retryAttempts = 0
})
.catch((error) => {
console.error('Error processing image:', error)
retryAttempts++
if (retryAttempts <= MAX_RETRY_ATTEMPTS) {
console.log(`Retrying (${retryAttempts}/${MAX_RETRY_ATTEMPTS})...`)
retryHandleImage(imageSrc)
} else {
console.log('Max retry attempts reached. Skipping image.')
retryAttempts = 0
clickGoButton()
}
})
}
function pollForScoreElement(callback) {
const interval = setInterval(() => {
const imageOverlayElement = document.querySelector('.image-container .image-overlay span.score')
if (imageOverlayElement) {
clearInterval(interval)
const pointsEarned = getPointsFromSpanText(imageOverlayElement.textContent)
const currentTotalScore = getTotalPointsFromHeaderText(
document.querySelector('.score-header .score').textContent
)
callback(pointsEarned, currentTotalScore)
}
}, 100)
}
function calculateAdjustedTimePerQuestion(totalPointsNeeded, questionsRemaining) {
const baseTime = 3000
const targetTime = 0
const timeRange = baseTime - targetTime
const adjustedTime = targetTime + (totalPointsNeeded / targetScore) * timeRange
const adjustedTimePerQuestion = adjustedTime / questionsRemaining
return Math.round(adjustedTimePerQuestion)
}
function retryHandleImage(imageSrc) {
setTimeout(() => handleNewImage(imageSrc), 100)
}
function chooseRandomAnswer() {
const choices = document.querySelectorAll('#game .answers .answer')
if (choices.length > 0) {
const randomChoice = choices[Math.floor(Math.random() * choices.length)]
randomChoice.click()
setTimeout(() => {
const correctAnswer = document.querySelector('#game .answers .is-right')
if (correctAnswer) {
const name = correctAnswer.textContent.trim()
people[currentImageHash] = name
localStorage.setItem(STORAGE_KEY, JSON.stringify(people))
console.log(`Discovered ${name} for hash ${currentImageHash}`)
} else {
console.error('Failed to discover the correct answer!')
}
}, 500)
} else {
console.error('No choices available to click.')
}
}
function startGame() {
const startButtonContainer = document.querySelector('.main-container.has-loaded .logo .rotation-loader')
if (startButtonContainer) {
startButtonContainer.click()
console.log('Clicked the start button')
} else {
console.error('Start button not found. Make sure the game is loaded.')
}
}
function restartGame() {
const replayButton = document.querySelector(
'.main-container .results-card button.button.mod-pill.palette-secondary.mod-XL'
)
if (replayButton) {
replayButton.click()
console.log('Clicked the replay button')
} else {
console.log('Please click "Replay" to restart the game.')
}
const goButton = document.querySelector(
'.main-container .results-card button.button.mod-pill.palette-primary.mod-L, .main-container .results-card button.button.mod-pill.palette-secondary.mod-XL'
)
if (goButton) {
goButton.click()
console.log('Clicked the go button')
} else {
console.log('Please click "Go" to restart the game.')
}
}
function waitForGameElement() {
const checkInterval = setInterval(() => {
const gameElement = document.querySelector('#game')
if (gameElement) {
clearInterval(checkInterval)
observeGame()
}
}, 200)
}
function observeGame() {
const gameElement = document.querySelector('#game')
if (!gameElement) {
console.error('Game element not found. Make sure the game is loaded.')
return
}
const observer = new MutationObserver(() => {
const imageElement = document.querySelector('#game app-timer .image')
if (imageElement) {
const imageSrcMatch = imageElement.style.backgroundImage.match(/url\("(.*)"\)/)
if (imageSrcMatch) {
const imageSrc = imageSrcMatch[1]
handleNewImage(imageSrc)
}
}
const gameEnded = !!document.querySelector('.main-container .results-card')
if (gameEnded) {
const headerText = document.querySelector('.main-container .results-card .header').textContent
const pointsEarned = getTotalPointsFromHeaderText(headerText)
totalPointsNeeded -= pointsEarned
if (questionsAnswered < 10) {
restartGame()
startGame()
} else {
console.log('Game completed. Total points achieved.')
}
}
})
observer.observe(gameElement, { childList: true, subtree: true })
}
waitForGameElement()
})()
now a new auto version ( just paste it when you see the Go button ) :
(() => {
const STORAGE_KEY = 'lucca_faces_data_v2';
let people = JSON.parse(localStorage.getItem(STORAGE_KEY)) || {};
let currentImageHash = '';
let retryAttempts = 0;
const MAX_RETRY_ATTEMPTS = 5; // Augmenter le nombre maximum de tentatives
})();