Esenjin e39e76fd06 ajout du "mode filou"
permet de faire tourner différents jetons d'accès pour outrepasser les limitations vraiment beaucoup trop stricts de certaines api
2025-03-22 13:42:21 +01:00

995 lines
38 KiB
JavaScript

/**
* FavMasToKey - Mode Filou (script de gestion des tokens multiples)
*/
// Attendre que le DOM soit chargé
document.addEventListener('DOMContentLoaded', function() {
// Éléments DOM
const uploadForm = document.getElementById('upload-form');
const jsonFileInput = document.getElementById('json-file');
const fileSummary = document.getElementById('file-summary');
const startMigration = document.getElementById('start-migration');
const pauseMigration = document.getElementById('pause-migration');
const cancelMigration = document.getElementById('cancel-migration');
const globalProgress = document.getElementById('global-progress');
const successProgress = document.getElementById('success-progress');
const skipProgress = document.getElementById('skip-progress');
const warnProgress = document.getElementById('warn-progress');
const errorProgress = document.getElementById('error-progress');
const operationLog = document.getElementById('operation-log');
const clearLog = document.getElementById('clear-log');
const statsDisplay = document.getElementById('stats-display');
// Sliders de configuration
const delayBetweenTokens = document.getElementById('delay-between-tokens');
const tokenCooldown = document.getElementById('token-cooldown');
const delayValue = document.getElementById('delay-value');
const cooldownValue = document.getElementById('cooldown-value');
const autoAdjust = document.getElementById('auto-adjust');
// Variables globales
let favoritesList = [];
let currentIndex = 0;
let totalItems = 0;
let isProcessing = false;
let isPaused = false;
let successCount = 0;
let errorCount = 0;
let skippedCount = 0;
let warningCount = 0;
// Variables pour la gestion des tokens
let tokens = [];
let tokensQueue = [];
let currentTokenIndex = 0;
let tokenTimers = {};
let activeRequests = 0;
let maxConcurrentRequests = 1; // Pour éviter les problèmes de race condition
// Options de migration
let migration = {
status: 'not_started',
startTime: null,
lastUpdateTime: null,
progress: {
current: 0,
total: 0,
percentage: 0
},
stats: {
success: 0,
error: 0,
skipped: 0,
warning: 0
},
options: {
delayBetweenTokens: 15, // secondes
tokenCooldown: 150, // secondes
autoAdjust: true
}
};
// ---- Gestionnaire d'événements ----
// Gérer les changements sur les sliders
if (delayBetweenTokens && delayValue) {
delayBetweenTokens.addEventListener('input', function() {
delayValue.textContent = this.value + 's';
migration.options.delayBetweenTokens = parseInt(this.value);
updateLocalStorage();
});
}
if (tokenCooldown && cooldownValue) {
tokenCooldown.addEventListener('input', function() {
cooldownValue.textContent = this.value + 's';
migration.options.tokenCooldown = parseInt(this.value);
updateLocalStorage();
});
}
if (autoAdjust) {
autoAdjust.addEventListener('change', function() {
migration.options.autoAdjust = this.checked;
updateLocalStorage();
});
}
// Gérer le téléchargement et l'analyse du fichier JSON
if (uploadForm) {
uploadForm.addEventListener('submit', function(e) {
e.preventDefault();
const file = jsonFileInput.files[0];
if (!file) {
alert('Veuillez sélectionner un fichier JSON.');
return;
}
// Vérifier l'extension du fichier
if (!file.name.endsWith('.json')) {
alert('Le fichier doit être au format JSON.');
return;
}
// Lire le fichier
const reader = new FileReader();
reader.onload = function(event) {
try {
const json = JSON.parse(event.target.result);
// Vérifier la structure du fichier
if (!json['@context'] || !json.type || !json.orderedItems) {
alert('Le format du fichier JSON n\'est pas celui attendu pour un export de favoris Mastodon.');
return;
}
favoritesList = json.orderedItems;
totalItems = favoritesList.length;
// Afficher un résumé
fileSummary.classList.remove('d-none');
fileSummary.innerHTML = `
<strong>${totalItems}</strong> favoris trouvés dans votre fichier Mastodon.
Le transfert sera effectué avec les jetons configurés.
`;
// Stocker les données dans localStorage pour les conserver
localStorage.setItem('favmastokey_favorites', JSON.stringify(favoritesList));
// Initialiser les données de migration
migration = {
status: 'not_started',
startTime: null,
lastUpdateTime: null,
progress: {
current: 0,
total: totalItems,
percentage: 0
},
stats: {
success: 0,
error: 0,
skipped: 0,
warning: 0
},
options: {
delayBetweenTokens: parseInt(delayBetweenTokens ? delayBetweenTokens.value : 15),
tokenCooldown: parseInt(tokenCooldown ? tokenCooldown.value : 150),
autoAdjust: autoAdjust ? autoAdjust.checked : true
}
};
// Sauvegarder les données de migration
updateLocalStorage();
// Scroll jusqu'à la section de migration
document.getElementById('step3').scrollIntoView({ behavior: 'smooth' });
} catch (error) {
alert('Erreur lors de l\'analyse du fichier JSON: ' + error.message);
}
};
reader.onerror = function() {
alert('Erreur lors de la lecture du fichier.');
};
reader.readAsText(file);
});
}
// Effacer le journal des opérations
if (clearLog) {
clearLog.addEventListener('click', function() {
operationLog.innerHTML = '';
});
}
// Démarrer la migration
if (startMigration) {
startMigration.addEventListener('click', function() {
if (isProcessing) return;
// Récupérer les favoris du localStorage si nécessaire
if (favoritesList.length === 0 && localStorage.getItem('favmastokey_favorites')) {
favoritesList = JSON.parse(localStorage.getItem('favmastokey_favorites'));
totalItems = favoritesList.length;
}
if (favoritesList.length === 0) {
addLogEntry('Aucun favori à migrer. Veuillez d\'abord télécharger votre fichier JSON.', 'error');
return;
}
// Vérifier s'il y a une migration en cours à reprendre
if (localStorage.getItem('favmastokey_multitoken_migration')) {
const savedMigration = JSON.parse(localStorage.getItem('favmastokey_multitoken_migration'));
// Si la migration était en cours ou en pause, proposer de la reprendre
if (savedMigration.status === 'in_progress' || savedMigration.status === 'paused') {
const resumeConfirm = confirm(`Une migration précédente a été trouvée (${savedMigration.progress.percentage.toFixed(1)}% terminée). Voulez-vous la reprendre?`);
if (resumeConfirm) {
// Restaurer l'état de la migration
migration = savedMigration;
currentIndex = migration.progress.current;
// Restaurer les statistiques
successCount = migration.stats.success || 0;
errorCount = migration.stats.error || 0;
skippedCount = migration.stats.skipped || 0;
warningCount = migration.stats.warning || 0;
// Mettre à jour l'interface
updateProgress(migration.progress.percentage);
updateStatistics();
} else {
// Réinitialiser la migration
resetMigration();
}
} else {
// Réinitialiser la migration
resetMigration();
}
} else {
// Initialiser une nouvelle migration
resetMigration();
}
// Initialiser les tokens
initializeTokens();
if (tokens.length === 0) {
addLogEntry('Aucun jeton d\'accès disponible. Veuillez configurer au moins un jeton.', 'error');
return;
}
// Démarrer la migration
isProcessing = true;
isPaused = false;
startMigration.classList.add('d-none');
pauseMigration.classList.remove('d-none');
// Initialiser le temps de démarrage si c'est une nouvelle migration
if (migration.status === 'not_started' || migration.startTime === null) {
migration.startTime = Date.now();
}
// Mettre à jour le statut de la migration
migration.status = 'in_progress';
updateLocalStorage();
// Afficher un message de démarrage
const tokenCountMsg = tokens.length === 1 ? '1 jeton' : `${tokens.length} jetons`;
addLogEntry(`Démarrage de la migration avec ${tokenCountMsg}. Délai entre jetons: ${migration.options.delayBetweenTokens}s, Délai de récupération: ${migration.options.tokenCooldown}s`, 'info');
// Lancer le processus de migration
startTokenRotation();
});
}
// Gérer la pause de la migration
if (pauseMigration) {
pauseMigration.addEventListener('click', function() {
if (!isProcessing) return;
isPaused = !isPaused;
if (isPaused) {
pauseMigration.textContent = 'Reprendre';
addLogEntry('Migration en pause.', 'warning');
// Mettre à jour le statut de la migration
migration.status = 'paused';
updateLocalStorage();
} else {
pauseMigration.textContent = 'Pause';
addLogEntry('Reprise de la migration...', 'info');
// Mettre à jour le statut de la migration
migration.status = 'in_progress';
updateLocalStorage();
// Reprendre le traitement
startTokenRotation();
}
});
}
// Gérer l'annulation de la migration
if (cancelMigration) {
cancelMigration.addEventListener('click', function() {
if (!isProcessing && currentIndex === 0) return;
const confirmCancel = confirm('Êtes-vous sûr de vouloir annuler la migration en cours ?');
if (confirmCancel) {
isProcessing = false;
isPaused = false;
addLogEntry('Migration annulée.', 'error');
// Nettoyer les timers
clearAllTimers();
// Réinitialiser l'interface
startMigration.classList.remove('d-none');
pauseMigration.classList.add('d-none');
pauseMigration.textContent = 'Pause';
// Réinitialiser les tokens
resetTokensStatus();
// Réinitialiser les données de migration
resetMigration();
}
});
}
// Vérifier si nous avons des données à restaurer au chargement
function initOnLoad() {
// Récupérer les favoris du localStorage
if (localStorage.getItem('favmastokey_favorites')) {
favoritesList = JSON.parse(localStorage.getItem('favmastokey_favorites'));
totalItems = favoritesList.length;
if (fileSummary) {
fileSummary.classList.remove('d-none');
fileSummary.innerHTML = `
<strong>${totalItems}</strong> favoris trouvés dans votre fichier Mastodon.
Le transfert sera effectué avec les jetons configurés.
`;
}
}
// Récupérer les données de migration
if (localStorage.getItem('favmastokey_multitoken_migration')) {
migration = JSON.parse(localStorage.getItem('favmastokey_multitoken_migration'));
currentIndex = migration.progress.current;
successCount = migration.stats.success || 0;
errorCount = migration.stats.error || 0;
skippedCount = migration.stats.skipped || 0;
warningCount = migration.stats.warning || 0;
// Mettre à jour l'interface
updateProgress(migration.progress.percentage);
updateStatistics();
// Restaurer les options
if (migration.options) {
if (delayBetweenTokens && migration.options.delayBetweenTokens) {
delayBetweenTokens.value = migration.options.delayBetweenTokens;
if (delayValue) {
delayValue.textContent = migration.options.delayBetweenTokens + 's';
}
}
if (tokenCooldown && migration.options.tokenCooldown) {
tokenCooldown.value = migration.options.tokenCooldown;
if (cooldownValue) {
cooldownValue.textContent = migration.options.tokenCooldown + 's';
}
}
if (autoAdjust && migration.options.autoAdjust !== undefined) {
autoAdjust.checked = migration.options.autoAdjust;
}
}
// Si la migration était en cours, proposer de la reprendre
if ((migration.status === 'in_progress' || migration.status === 'paused') &&
migration.progress.current < migration.progress.total) {
const timeAgo = getTimeAgoString(new Date(migration.lastUpdateTime));
// Ajouter une entrée au journal
addLogEntry(`Une migration précédente a été trouvée (${migration.progress.percentage.toFixed(1)}% terminée, ${timeAgo}). Utilisez le bouton "Démarrer la migration" pour la reprendre.`, 'info');
}
}
}
// Initialiser les tokens à partir des éléments du DOM
function initializeTokens() {
tokens = [];
tokensQueue = [];
// Ajouter le token principal
tokens.push({
id: 'primary',
name: 'Token principal',
status: 'ready',
lastUsed: null,
cooldownUntil: null,
errorCount: 0,
element: document.querySelector('.token-card:first-child')
});
// Ajouter les tokens supplémentaires
const tokenCards = document.querySelectorAll('.token-card[data-token-id]');
tokenCards.forEach(card => {
const tokenId = card.getAttribute('data-token-id');
tokens.push({
id: tokenId,
name: card.querySelector('strong').textContent,
status: 'ready',
lastUsed: null,
cooldownUntil: null,
errorCount: 0,
element: card
});
});
// Initialiser la file d'attente avec tous les tokens
resetTokensQueue();
}
// Réinitialiser la file d'attente des tokens
function resetTokensQueue() {
tokensQueue = [...tokens]; // Copie de tous les tokens
// Mélanger pour éviter d'utiliser toujours le même ordre
shuffleArray(tokensQueue);
}
// Mélanger un tableau (algorithme de Fisher-Yates)
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;
}
// Démarrer la rotation des tokens
function startTokenRotation() {
if (!isProcessing || isPaused) {
return;
}
// Vérifier si nous avons terminé
if (currentIndex >= totalItems) {
finishMigration();
return;
}
// Si la file d'attente est vide, la réinitialiser
if (tokensQueue.length === 0) {
resetTokensQueue();
}
// Vérifier si on a un token disponible
const availableToken = findAvailableToken();
if (availableToken) {
// Traiter le prochain favori avec ce token
processWithToken(availableToken);
} else {
// Aucun token disponible actuellement, attendre et réessayer
addLogEntry('Tous les jetons sont en cours d\'utilisation ou en récupération. Attente...', 'info');
setTimeout(startTokenRotation, 5000);
}
}
// Trouver un token disponible
function findAvailableToken() {
const now = Date.now();
// Vérifier d'abord la file d'attente
for (let i = 0; i < tokensQueue.length; i++) {
const token = tokensQueue[i];
// Vérifier si le token est prêt
if (token.status === 'ready' &&
(!token.cooldownUntil || now >= token.cooldownUntil)) {
// Retirer ce token de la file d'attente
tokensQueue.splice(i, 1);
return token;
}
}
// Si aucun token n'est disponible dans la file d'attente,
// vérifier tous les tokens (au cas où un token aurait fini son cooldown)
for (const token of tokens) {
if (token.status === 'ready' &&
(!token.cooldownUntil || now >= token.cooldownUntil)) {
// Retirer ce token de la file d'attente s'il y est encore
const queueIndex = tokensQueue.findIndex(t => t.id === token.id);
if (queueIndex !== -1) {
tokensQueue.splice(queueIndex, 1);
}
return token;
}
}
// Aucun token disponible
return null;
}
// Traiter un favori avec un token spécifique
function processWithToken(token) {
if (!isProcessing || isPaused) {
return;
}
// Mettre à jour le statut du token
updateTokenStatus(token, 'active');
// URL à traiter
const url = favoritesList[currentIndex];
// Ajouter une entrée dans le journal
addLogEntry(`[${token.name}] Traitement de: ${getTruncatedUrl(url)}`, 'info');
// Envoyer la requête au serveur
fetch('process_multitoken.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
url: url,
token_id: token.id,
current_index: currentIndex,
total_items: totalItems
})
})
.then(response => response.json())
.then(data => {
// Mettre à jour les statistiques en fonction du résultat
handleProcessResult(data, token, url);
// Mettre à jour l'index
currentIndex++;
// Mettre à jour la progression
updateProgress();
// Mettre le token en cooldown
const cooldownPeriod = migration.options.tokenCooldown * 1000; // Convertir en millisecondes
const cooldownUntil = Date.now() + cooldownPeriod;
token.lastUsed = Date.now();
token.cooldownUntil = cooldownUntil;
updateTokenStatus(token, 'cooldown');
// Programmer la réactivation du token après le cooldown
tokenTimers[token.id] = setTimeout(() => {
// Remettre le token en état "prêt"
updateTokenStatus(token, 'ready');
// Ajouter le token à la fin de la file d'attente
tokensQueue.push(token);
}, cooldownPeriod);
// Démarrer le traitement du prochain favori après un délai
setTimeout(startTokenRotation, migration.options.delayBetweenTokens * 1000);
})
.catch(error => {
console.error('Erreur lors du traitement:', error);
// Ajouter l'erreur au journal
addLogEntry(`[${token.name}] Erreur lors du traitement: ${error.message}`, 'error');
// Incrémenter le compteur d'erreurs du token
token.errorCount = (token.errorCount || 0) + 1;
// Si trop d'erreurs avec ce token, le mettre en erreur
if (token.errorCount >= 3) {
updateTokenStatus(token, 'error');
addLogEntry(`[${token.name}] Ce jeton rencontre des problèmes répétés et a été désactivé.`, 'error');
} else {
// Sinon, mettre le token en cooldown plus court
const cooldownPeriod = 30 * 1000; // 30 secondes
const cooldownUntil = Date.now() + cooldownPeriod;
token.lastUsed = Date.now();
token.cooldownUntil = cooldownUntil;
updateTokenStatus(token, 'cooldown');
// Programmer la réactivation du token après le cooldown
tokenTimers[token.id] = setTimeout(() => {
updateTokenStatus(token, 'ready');
tokensQueue.push(token);
}, cooldownPeriod);
}
// Augmenter le compteur d'erreurs
errorCount++;
updateStatistics();
// Continuer avec le prochain token après un délai
setTimeout(startTokenRotation, migration.options.delayBetweenTokens * 1000);
});
}
// Gérer le résultat du traitement
function handleProcessResult(data, token, url) {
if (data.success) {
// Résultat du traitement
const result = data.result;
// Construction du message avec détails si disponibles
let message = result.message;
if (result.details) {
message += ` (${result.details})`;
}
// Ajouter l'entrée au journal avec le nom du token
addLogEntry(`[${token.name}] ${message}`, result.status);
// Mettre à jour les compteurs
if (result.status === 'success') {
successCount++;
token.errorCount = 0; // Réinitialiser le compteur d'erreurs
} else if (result.status === 'error') {
errorCount++;
// Incrémenter le compteur d'erreurs du token
token.errorCount = (token.errorCount || 0) + 1;
// Si autoAdjust est activé et qu'il y a beaucoup d'erreurs, augmenter les délais
if (migration.options.autoAdjust && token.errorCount >= 2) {
if (result.error_type === 'rate_limit') {
// Augmenter le délai entre les tokens
if (migration.options.delayBetweenTokens < 60) {
migration.options.delayBetweenTokens += 5;
if (delayBetweenTokens) delayBetweenTokens.value = migration.options.delayBetweenTokens;
if (delayValue) delayValue.textContent = migration.options.delayBetweenTokens + 's';
addLogEntry(`Délai entre jetons automatiquement augmenté à ${migration.options.delayBetweenTokens}s en raison de limitations d'API.`, 'warning');
}
// Augmenter le délai de cooldown
if (migration.options.tokenCooldown < 300) {
migration.options.tokenCooldown += 30;
if (tokenCooldown) tokenCooldown.value = migration.options.tokenCooldown;
if (cooldownValue) cooldownValue.textContent = migration.options.tokenCooldown + 's';
addLogEntry(`Délai de récupération automatiquement augmenté à ${migration.options.tokenCooldown}s.`, 'warning');
}
// Mettre à jour le localStorage
updateLocalStorage();
}
}
} else if (result.status === 'info') {
skippedCount++;
token.errorCount = 0; // Réinitialiser le compteur d'erreurs
} else if (result.status === 'warning') {
warningCount++;
// Si c'est une erreur de rate limit, incrémenter le compteur d'erreurs
if (result.error_type === 'rate_limit') {
token.errorCount = (token.errorCount || 0) + 1;
}
}
// Mettre à jour les statistiques
updateStatistics();
} else {
// Erreur lors du traitement
addLogEntry(`[${token.name}] Erreur: ${data.message}`, 'error');
errorCount++;
updateStatistics();
// Incrémenter le compteur d'erreurs du token
token.errorCount = (token.errorCount || 0) + 1;
}
}
// Mettre à jour le statut d'un token
function updateTokenStatus(token, status) {
token.status = status;
if (token.element) {
// Mettre à jour la classe CSS
token.element.classList.remove('active', 'cooldown', 'error');
// Mettre à jour l'indicateur de statut
const statusIndicator = token.element.querySelector('.token-status');
const statusText = token.element.querySelector('.text-muted.small');
if (statusIndicator) {
statusIndicator.classList.remove('ready', 'cooldown', 'error', 'idle');
}
switch (status) {
case 'active':
token.element.classList.add('active');
if (statusIndicator) statusIndicator.classList.add('ready');
if (statusText) statusText.textContent = 'En cours d\'utilisation';
break;
case 'cooldown':
token.element.classList.add('cooldown');
if (statusIndicator) statusIndicator.classList.add('cooldown');
if (statusText && token.cooldownUntil) {
const cooldownSeconds = Math.ceil((token.cooldownUntil - Date.now()) / 1000);
statusText.textContent = `Récupération: ${cooldownSeconds}s`;
// Mettre à jour le compteur de cooldown toutes les secondes
const updateInterval = setInterval(() => {
const remainingSeconds = Math.ceil((token.cooldownUntil - Date.now()) / 1000);
if (remainingSeconds <= 0) {
clearInterval(updateInterval);
if (statusText) statusText.textContent = 'Prêt';
} else {
if (statusText) statusText.textContent = `Récupération: ${remainingSeconds}s`;
}
}, 1000);
}
break;
case 'error':
token.element.classList.add('error');
if (statusIndicator) statusIndicator.classList.add('error');
if (statusText) statusText.textContent = 'Erreur';
break;
case 'ready':
default:
if (statusIndicator) statusIndicator.classList.add('ready');
if (statusText) statusText.textContent = 'Prêt';
break;
}
}
}
// Réinitialiser le statut de tous les tokens
function resetTokensStatus() {
tokens.forEach(token => {
updateTokenStatus(token, 'ready');
token.lastUsed = null;
token.cooldownUntil = null;
token.errorCount = 0;
});
}
// Nettoyer tous les timers
function clearAllTimers() {
Object.keys(tokenTimers).forEach(tokenId => {
clearTimeout(tokenTimers[tokenId]);
delete tokenTimers[tokenId];
});
}
// Obtenir une version tronquée d'une URL pour l'affichage
function getTruncatedUrl(url) {
if (url.length <= 60) return url;
const urlObj = new URL(url);
const domain = urlObj.hostname;
const path = urlObj.pathname;
const lastSegment = path.split('/').pop();
return `${domain}/.../${lastSegment}`;
}
// Générer une chaîne "il y a X minutes/heures" à partir d'une date
function getTimeAgoString(date) {
const now = new Date();
const diffMs = now - date;
const diffSeconds = Math.floor(diffMs / 1000);
if (diffSeconds < 60) {
return `il y a ${diffSeconds} seconde${diffSeconds > 1 ? 's' : ''}`;
}
const diffMinutes = Math.floor(diffSeconds / 60);
if (diffMinutes < 60) {
return `il y a ${diffMinutes} minute${diffMinutes > 1 ? 's' : ''}`;
}
const diffHours = Math.floor(diffMinutes / 60);
if (diffHours < 24) {
return `il y a ${diffHours} heure${diffHours > 1 ? 's' : ''}`;
}
const diffDays = Math.floor(diffHours / 24);
return `il y a ${diffDays} jour${diffDays > 1 ? 's' : ''}`;
}
/**
* Réinitialise les données de migration
*/
function resetMigration() {
currentIndex = 0;
successCount = 0;
errorCount = 0;
skippedCount = 0;
warningCount = 0;
migration = {
status: 'not_started',
startTime: null,
lastUpdateTime: null,
progress: {
current: 0,
total: totalItems,
percentage: 0
},
stats: {
success: 0,
error: 0,
skipped: 0,
warning: 0
},
options: {
delayBetweenTokens: parseInt(delayBetweenTokens ? delayBetweenTokens.value : 15),
tokenCooldown: parseInt(tokenCooldown ? tokenCooldown.value : 150),
autoAdjust: autoAdjust ? autoAdjust.checked : true
}
};
// Mettre à jour le localStorage
updateLocalStorage();
// Mettre à jour l'interface
updateProgress(0);
updateStatistics();
}
/**
* Met à jour les données de migration dans localStorage
*/
function updateLocalStorage() {
migration.lastUpdateTime = Date.now();
migration.progress = {
current: currentIndex,
total: totalItems,
percentage: (currentIndex / totalItems) * 100
};
migration.stats = {
success: successCount,
error: errorCount,
skipped: skippedCount,
warning: warningCount
};
if (delayBetweenTokens && tokenCooldown && autoAdjust) {
migration.options = {
delayBetweenTokens: parseInt(delayBetweenTokens.value),
tokenCooldown: parseInt(tokenCooldown.value),
autoAdjust: autoAdjust.checked
};
}
// Sauvegarder dans localStorage
localStorage.setItem('favmastokey_multitoken_migration', JSON.stringify(migration));
}
/**
* Terminer la migration avec succès
*/
function finishMigration() {
isProcessing = false;
// Nettoyer les timers
clearAllTimers();
const summary = `Migration terminée ! ${successCount} publications ajoutées aux favoris, ${errorCount} échecs, ${skippedCount} déjà présentes, ${warningCount} avertissements.`;
addLogEntry(summary, 'success');
startMigration.classList.remove('d-none');
startMigration.textContent = 'Terminer';
startMigration.addEventListener('click', function onceFinished() {
// Nettoyer localStorage
localStorage.removeItem('favmastokey_multitoken_migration');
// Ne pas supprimer les favoris pour permettre une nouvelle migration si nécessaire
// localStorage.removeItem('favmastokey_favorites');
// Réinitialiser l'interface
resetTokensStatus();
startMigration.textContent = 'Démarrer la migration';
// Retirer cet écouteur d'événement pour éviter les doublons
startMigration.removeEventListener('click', onceFinished);
// Scroll vers le haut
window.scrollTo({ top: 0, behavior: 'smooth' });
});
pauseMigration.classList.add('d-none');
// Mettre à jour la progression à 100%
updateProgress(100);
// Mettre à jour le statut de la migration
migration.status = 'completed';
updateLocalStorage();
}
/**
* Met à jour la barre de progression
*/
function updateProgress(forcedValue = null) {
const progress = forcedValue !== null ? forcedValue : (currentIndex / totalItems) * 100;
if (globalProgress) {
globalProgress.style.width = progress + '%';
globalProgress.textContent = Math.round(progress) + '%';
globalProgress.setAttribute('aria-valuenow', progress);
}
// Mettre à jour le localStorage
updateLocalStorage();
}
/**
* Met à jour les statistiques de progression
*/
function updateStatistics() {
// Calculer les pourcentages pour les barres de progression
const total = successCount + errorCount + skippedCount + warningCount;
const successPct = total > 0 ? (successCount / total) * 100 : 0;
const skipPct = total > 0 ? (skippedCount / total) * 100 : 0;
const warnPct = total > 0 ? (warningCount / total) * 100 : 0;
const errorPct = total > 0 ? (errorCount / total) * 100 : 0;
// Mettre à jour les barres
if (successProgress) {
successProgress.style.width = successPct + '%';
successProgress.setAttribute('aria-valuenow', successPct);
}
if (skipProgress) {
skipProgress.style.width = skipPct + '%';
skipProgress.setAttribute('aria-valuenow', skipPct);
}
if (warnProgress) {
warnProgress.style.width = warnPct + '%';
warnProgress.setAttribute('aria-valuenow', warnPct);
}
if (errorProgress) {
errorProgress.style.width = errorPct + '%';
errorProgress.setAttribute('aria-valuenow', errorPct);
}
// Mettre à jour le texte des statistiques
if (statsDisplay) {
statsDisplay.textContent = `${successCount} succès, ${skippedCount} ignorés, ${warningCount} avertissements, ${errorCount} erreurs`;
}
// Mettre à jour le localStorage
updateLocalStorage();
}
/**
* Ajoute une entrée dans le journal des opérations
*/
function addLogEntry(message, status = 'info') {
if (!operationLog) return;
const entry = document.createElement('div');
entry.className = `log-entry ${status}`;
const timestamp = new Date().toLocaleTimeString();
entry.textContent = `[${timestamp}] ${message}`;
operationLog.appendChild(entry);
// Scroll vers le bas pour voir la dernière entrée
const logContainer = document.getElementById('log-container');
if (logContainer) {
logContainer.scrollTop = logContainer.scrollHeight;
}
}
// Initialiser la page au chargement
initOnLoad();
});