Greasy Fork is available in English.

Altaireh TikTok Video Downloader V4

Download TikTok Videos Without A Watermark

// ==UserScript==
// @name         Altaireh TikTok Video Downloader V4
// @namespace    none
// @version      4.0
// @description  Download TikTok Videos Without A Watermark
// @author       altaireh
// @match        *://*.tiktok.com/*
// @icon         http://i.hmp.me/m/a0a089663971fa7ca3e9cdc2264ce7b4.png
// @grant        GM_xmlhttpRequest
// @grant        GM_download
// @grant        GM_getResourceURL
// @resource     BUTTON_IMG http://i.hmp.me/m/3025e92fff4d4d7fa8e0642fb6cfe270.png
// ==/UserScript==

(function() {
    'use strict';

    const FILE_NAME = 'Altaireh.mp4';

    const download = (url) => GM_xmlhttpRequest({
        method: 'GET',
        url,
        responseType: 'blob',
        onload: ({ response }) => GM_download({
            url: URL.createObjectURL(response),
            name: FILE_NAME
        })
    });

    const manageVideoButtons = (video) => {
        let button;
        const buttonActions = {
            mouseover: () => {
                if (!button) {
                    button = document.createElement('img');
                    button.src = GM_getResourceURL('BUTTON_IMG');
                    button.style.cssText = 'position: absolute; left: 10px; top: 50%; transform: translateY(-50%); z-index: 1000; width: 50px; height: 50px; cursor: pointer;';
                    button.onclick = (e) => {
                        e.stopPropagation();
                        e.preventDefault();
                        download(video.src || video.querySelector('source')?.src);
                    };
                    video.parentNode.appendChild(button);
                }
            },
            mouseout: (e) => {
                if (!video.contains(e.relatedTarget) && (!button || !button.contains(e.relatedTarget))) {
                    button.remove();
                    button = null;
                }
            }
        };
        Object.keys(buttonActions).forEach(event => video.addEventListener(event, buttonActions[event]));
    };

    new MutationObserver(() => {
        document.querySelectorAll('video:not(.processed)').forEach((video) => {
            video.classList.add('processed');
            manageVideoButtons(video);
        });
    }).observe(document.body, { childList: true, subtree: true });
})();