// ==UserScript==
// @name Tiktok Video & Slideshow Downloader 🎬🖼️
// @namespace http://greasyfork.icu/en/scripts/431826
// @version 2.3
// @description Download TikTok videos without watermark and slideshow images
// @author YAD
// @match *://*.tiktok.com/*
// @grant GM_xmlhttpRequest
// @grant GM_download
// @require https://cdnjs.cloudflare.com/ajax/libs/jszip/3.7.1/jszip.min.js
// @icon https://miro.medium.com/v2/resize:fit:512/1*KX6NTUUHWlCP4sCXz28TBA.png
// @license MIT
// ==/UserScript==
(function () {
'use strict';
const getFileName = (url, type) => {
const id = url.split('/').pop().split('?')[0];
return type === 'video' ? `TikTok_Video_${id}.mp4` : `TikTok_Image_${id}.jpeg`;
};
const downloadFile = (url, type, button) => {
if (!url) {
button.textContent = '✖️';
return;
}
button.textContent = '⏳';
GM_xmlhttpRequest({
method: 'GET',
url,
responseType: 'blob',
onload: ({ response }) => {
if (response) {
GM_download({
url: URL.createObjectURL(response),
name: getFileName(url, type),
onload: () => {
button.textContent = '✔️';
setTimeout(() => button.remove(), 2000);
},
onerror: () => {
button.textContent = '✖️';
setTimeout(() => button.remove(), 1500);
}
});
} else {
button.textContent = '✖️';
setTimeout(() => button.remove(), 1500);
}
},
onerror: () => {
button.textContent = '✖️';
setTimeout(() => button.remove(), 1500);
},
ontimeout: () => {
button.textContent = '✖️';
setTimeout(() => button.remove(), 1500);
}
});
};
const createDownloadButton = (video) => {
const button = document.createElement('div');
Object.assign(button.style, {
position: 'absolute',
right: '15px',
top: '27%',
transform: 'translateY(-50%)',
width: '50px',
height: '50px',
backgroundColor: '#ff3b5c',
color: '#ffffff',
fontSize: '18px',
textShadow: '3px 3px 0px #9C1331',
textAlign: 'center',
lineHeight: '50px',
borderRadius: '50%',
cursor: 'pointer',
zIndex: '99999'
});
button.textContent = '🎞️';
button.onclick = async (e) => {
e.stopPropagation();
e.preventDefault();
button.textContent = '⏳';
const videoUrl = video.src || video.querySelector('source')?.src;
if (videoUrl && videoUrl.startsWith('blob:')) {
button.style.backgroundColor = '#ffa700';
const xgwrapper = document.querySelector('[id^="xgwrapper-"]');
const videoId = xgwrapper?.id.split('-')[2];
const tiktokVideoUrl = `https://www.tiktok.com/@YAD/video/${videoId}`;
const iframe = document.createElement('iframe');
iframe.style.position = 'fixed';
iframe.style.visibility = 'hidden';
iframe.src = tiktokVideoUrl;
document.body.appendChild(iframe);
const checkVideoUrl = () => {
const iframeDocument = iframe.contentDocument || iframe.contentWindow.document;
const videoElement = iframeDocument.querySelector('video');
if (videoElement && !videoElement.src.startsWith('blob:')) {
downloadFile(videoElement.src, 'video', button);
iframe.remove();
} else {
setTimeout(checkVideoUrl, 1000);
}
};
setTimeout(checkVideoUrl, 8000);
} else {
button.style.backgroundColor = '#ff3b5c';
downloadFile(videoUrl, 'video', button);
}
};
video.parentNode.style.position = 'relative';
video.parentNode.appendChild(button);
return button;
};
const manageDownloadButtons = (video) => {
let button;
video.addEventListener('mouseover', () => {
if (!button) {
button = createDownloadButton(video);
}
});
video.addEventListener('mouseout', (e) => {
if (button && !video.contains(e.relatedTarget) && !button.contains(e.relatedTarget)) {
button.remove();
button = null;
}
});
};
const checkForNewVideos = () => {
document.querySelectorAll('video:not(.processed)').forEach((video) => {
video.classList.add('processed');
manageDownloadButtons(video);
});
requestAnimationFrame(checkForNewVideos);
};
const promptUserForZipOrIndividual = () => {
return new Promise((resolve) => {
const userChoice = confirm("Do you want to download images as a ZIP file? Cancel will download individually");
resolve(userChoice);
});
};
const addImageDownloadButton = (container) => {
if (container.querySelector('.image-download-btn')) return;
const button = document.createElement('div');
button.className = 'image-download-btn';
button.textContent = '🖼️';
Object.assign(button.style, {
position: 'absolute',
right: '15px',
top: '27%',
transform: 'translateY(-50%)',
width: '50px',
height: '50px',
backgroundColor: '#16b1c6',
color: '#ffffff',
fontSize: '18px',
textShadow: '3px 3px 0px #9C1331',
textAlign: 'center',
lineHeight: '50px',
borderRadius: '50%',
cursor: 'pointer',
zIndex: '999999'
});
container.style.position = 'relative';
container.appendChild(button);
button.onclick = async () => {
button.textContent = '⌛';
const images = container.querySelectorAll('img');
if (!images.length) {
alert("No images found!");
button.textContent = '✖️';
return;
}
const imageUrls = Array.from(images).map(img => img.src);
const uniqueUrls = [...new Set(imageUrls)];
const downloadAsZip = await promptUserForZipOrIndividual();
if (downloadAsZip) {
const zip = new JSZip();
let count = 0;
uniqueUrls.forEach((url, index) => {
GM_xmlhttpRequest({
method: 'GET',
url,
responseType: 'blob',
onload: (response) => {
zip.file(`image${index + 1}.jpeg`, response.response);
count++;
if (count === uniqueUrls.length) {
zip.generateAsync({ type: 'blob' }).then((content) => {
const url = URL.createObjectURL(content);
GM_download({ url, name: 'TikTok_Slideshow.zip' });
button.textContent = '✔️';
});
}
}
});
});
} else {
uniqueUrls.forEach((url, index) => {
GM_download({
url,
name: `image${index + 1}.jpeg`,
onload: () => {
button.textContent = '✔️';
}
});
});
}
};
};
const checkForImageSlideshows = () => {
document.querySelectorAll('.css-kgj69c-DivPhotoVideoContainer:not(.processed)').forEach((container) => {
container.classList.add('processed');
addImageDownloadButton(container);
});
requestAnimationFrame(checkForImageSlideshows);
};
requestAnimationFrame(checkForNewVideos);
requestAnimationFrame(checkForImageSlideshows);
})();