Greasy Fork is available in English.
Un script pour facebook.com permettant de télécharger des vidéos ou des collections de vidéos.
// ==UserScript== // @name FB Video Saver // @match https://www.facebook.com/* // @grant GM_registerMenuCommand // @version 1.0 // @author Macxzew // @description Un script pour facebook.com permettant de télécharger des vidéos ou des collections de vidéos. // @license MIT // @namespace https://greasyfork.org/users/1425005 // ==/UserScript== (async () => { 'use strict'; const processedLinks = new Set(); const processedVideos = new Set(); let videoDetails = null; const addUIButton = () => { setTimeout(() => { const button = document.createElement('button'); button.textContent = 'Télécharger'; Object.assign(button.style, { position: 'fixed', top: '1%', right: '20%', backgroundColor: '#007bff', color: 'white', padding: '10px 20px', border: 'none', borderRadius: '5px', boxShadow: '0 4px 6px rgba(0, 0, 0, 0.1)', cursor: 'pointer', zIndex: '9999', }); button.addEventListener('click', () => { const url = window.location.href; if (url.includes('/saved/')) { processSavedVideos(); } else if (url.includes('/reel/') || url.includes('/videos/') || url.includes('/watch/')) { downloadVisibleVideo(); } else { alert('Aucune action disponible pour cette page.'); } }); document.body.appendChild(button); }, 2500); }; const createNotification = (message) => { const notification = document.createElement('div'); notification.textContent = message; Object.assign(notification.style, { position: 'fixed', top: '20px', right: '20px', backgroundColor: 'rgba(0, 128, 0, 0.9)', color: 'white', padding: '10px 20px', borderRadius: '5px', boxShadow: '0 4px 6px rgba(0, 0, 0, 0.1)', fontSize: '14px', zIndex: '9999', animation: 'fadeout 3s forwards', }); document.body.appendChild(notification); setTimeout(() => { notification.remove(); }, 3000); const style = document.createElement('style'); style.textContent = ` @keyframes fadeout { 0% { opacity: 1; } 80% { opacity: 1; } 100% { opacity: 0; } } `; document.head.appendChild(style); }; const collectVideoDetails = async () => { const url = window.location.href; if (!url.includes('/reel/') && !url.includes('/videos/') && !url.includes('/watch/')) { console.log('Cette page ne contient pas de contenu vidéo pertinent.'); return; } videoDetails = null; const video = document.querySelector('video'); if (!video) return console.error('Aucune vidéo visible n\'a été trouvée.'); const reactPropsKey = Object.keys(video.parentElement).find(key => key.startsWith('__reactProps')); const videoFBID = video.parentElement[reactPropsKey]?.children?.props?.videoFBID; if (!videoFBID) return console.error('Impossible de récupérer l\'identifiant de la vidéo.'); const requestBody = new URLSearchParams({ doc_id: '5279476072161634', variables: JSON.stringify({ videoID: videoFBID }), fb_dtsg: require('DTSGInitialData').token, }); const response = await fetch('https://www.facebook.com/api/graphql/', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: requestBody.toString(), }); const data = JSON.parse((await response.text()).split('\n')[0])?.data?.video; const videoURL = data?.playable_url_quality_hd || data?.playable_url; if (!videoURL) return console.error('Impossible de récupérer l\'URL de la vidéo.'); videoDetails = { url: videoURL, ready: true }; }; const downloadVisibleVideo = async () => { if (!videoDetails || !videoDetails.ready) return false; const blob = await (await fetch(videoDetails.url)).blob(); const anchor = document.createElement('a'); anchor.href = URL.createObjectURL(blob); anchor.download = 'video.mp4'; document.body.appendChild(anchor); anchor.click(); anchor.remove(); createNotification('La vidéo a été téléchargée avec succès.'); return true; }; const processSavedVideos = async () => { const url = window.location.href; if (!url.includes('/saved/')) return; let skipNext = false; const getVideoLinks = () => { return Array.from(document.querySelectorAll('a')) .map(a => a.href) .filter(href => (href.includes('/watch/') || href.includes('/reel/') || href.includes('/videos/'))) .filter(href => !processedLinks.has(href)); // Filtrer les liens déjà traités }; const refreshPageContent = async () => { let lastHeight = document.body.scrollHeight, timer; while (true) { window.scrollTo(0, document.body.scrollHeight); await new Promise(resolve => { clearTimeout(timer); timer = setTimeout(resolve, 5000); }); const currentHeight = document.body.scrollHeight; if (currentHeight === lastHeight) break; lastHeight = currentHeight; } }; await refreshPageContent(); // Charger tous les liens visibles au départ let links = getVideoLinks(); for (const link of links) { if (processedLinks.has(link)) continue; // Éviter les doublons if (skipNext) { console.log(`Lien sauté : ${link}`); skipNext = false; // Réinitialiser après avoir sauté un lien continue; } const newTab = window.open(link, '_blank'); if (!newTab) continue; try { await new Promise(resolve => { const interval = setInterval(() => { if (newTab.document.readyState === 'complete') { clearInterval(interval); resolve(); } }, 500); }); if (processedLinks.has(link)) { console.log(`Lien déjà téléchargé : ${link}`); newTab.close(); continue; } const success = await downloadVideoFromDocument(newTab.document); if (success) { processedLinks.add(link); // Ajouter à la liste des liens traités // Vérifier le type de lien pour décider de sauter ou non if (link.includes('/reel/') || link.includes('/watch/')) { console.log(`Lien de type "reel" ou "watch" détecté, saut du prochain lien.`); skipNext = true; } } newTab.close(); } catch (error) { console.error(`Erreur lors du traitement du lien : ${link}`, error); newTab.close(); } // Réactualiser la liste des liens après chaque téléchargement console.log('Réactualisation des liens visibles...'); await refreshPageContent(); links = getVideoLinks(); } // Effacer les liens traités de la liste processedLinks.clear(); console.log('Liste des liens traités effacée.'); // Actualiser la page principale "saved" console.log('Actualisation de la page principale...'); window.location.reload(); }; const downloadVideoFromDocument = async (doc) => { const video = doc.querySelector('video'); if (!video) return console.error('Aucune vidéo visible n\'a été trouvée.'), false; const reactPropsKey = Object.keys(video.parentElement).find(key => key.startsWith('__reactProps')); const videoFBID = video.parentElement[reactPropsKey]?.children?.props?.videoFBID; if (!videoFBID) return console.error('Impossible de récupérer l\'identifiant de la vidéo.'), false; const requestBody = new URLSearchParams({ doc_id: '5279476072161634', variables: JSON.stringify({ videoID: videoFBID }), fb_dtsg: require('DTSGInitialData').token, }); const response = await fetch('https://www.facebook.com/api/graphql/', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: requestBody.toString(), }); const data = JSON.parse((await response.text()).split('\n')[0])?.data?.video; const videoURL = data?.playable_url_quality_hd || data?.playable_url; if (!videoURL || processedVideos.has(videoURL)) return console.error('Impossible de récupérer l\'URL de la vidéo.'), false; processedVideos.add(videoURL); const blob = await (await fetch(videoURL)).blob(); const anchor = document.createElement('a'); anchor.href = URL.createObjectURL(blob); anchor.download = 'video.mp4'; doc.body.appendChild(anchor); anchor.click(); anchor.remove(); return true; }; window.addEventListener('load', () => { collectVideoDetails(); addUIButton(); }); window.addEventListener('popstate', collectVideoDetails); ((history) => { const originalPushState = history.pushState; const originalReplaceState = history.replaceState; history.pushState = function (...args) { const r###lt = originalPushState.apply(this, args); window.dispatchEvent(new Event('pushstate')); return r###lt; }; history.replaceState = function (...args) { const r###lt = originalReplaceState.apply(this, args); window.dispatchEvent(new Event('replacestate')); return r###lt; }; })(window.history); const previousUrl = { value: window.location.href }; const checkUrlChange = () => { const currentUrl = window.location.href; if (currentUrl.includes('/saved/') && !previousUrl.value.includes('/saved/')) { console.log('L\'URL a changé vers "saved". Actualisation de la page...'); window.location.reload(); } previousUrl.value = currentUrl; }; window.addEventListener('pushstate', () => { collectVideoDetails(); checkUrlChange(); }); window.addEventListener('replacestate', () => { collectVideoDetails(); checkUrlChange(); }); window.addEventListener('popstate', () => { collectVideoDetails(); checkUrlChange(); }); setInterval(checkUrlChange, 500); // Vérifier les changements d'URL régulièrement GM_registerMenuCommand('Download Video', () => { const url = window.location.href; if (!url.includes('/saved/') && !url.includes('/videos/') && !url.includes('/watch/')) { alert('Aucune action disponible pour cette page.'); } else { downloadVisibleVideo(); } }); GM_registerMenuCommand('Process Saved Videos', processSavedVideos); })();