🟢 SaveTheVideo Auto-Start & Direct Download

Companion script for my X.com Video Downloader. Automatically clicks “Start”, blocks pop-outs, and downloads the video with an animated overlay and checkmark. Requires the X.com Video Downloader script to function properly.

// ==UserScript==
// @name         🟢 SaveTheVideo Auto-Start & Direct Download
// @version      1.1.0
// @description  Companion script for my X.com Video Downloader. Automatically clicks “Start”, blocks pop-outs, and downloads the video with an animated overlay and checkmark. Requires the X.com Video Downloader script to function properly.
// @match        https://www.savethevideo.com/downloader*
// @author       jayfantz
// @grant        unsafeWindow
// @run-at       document-start
// @namespace https://greasyfork.org/users/1429572
// ==/UserScript==

// NOTE: This script works only when triggered from the X.com Video Downloader userscript.

(function() {
  'use strict';

  let downloadStarted = false;
  let overlay, pulseDot, messageBox;

  // ---------- Overlay Notification ----------
  function notify(msg) {
    overlay = document.createElement('div');
    Object.assign(overlay.style, {
      position: 'fixed',
      inset: 0,
      background: 'rgba(0,0,0,0.6)',
      display: 'flex',
      justifyContent: 'center',
      alignItems: 'center',
      zIndex: 999999
    });

    const box = document.createElement('div');
    Object.assign(box.style, {
      background: '#1e1e1e',
      color: '#eee',
      padding: '24px 32px',
      borderRadius: '12px',
      fontFamily: 'system-ui, sans-serif',
      fontSize: '16px',
      textAlign: 'center',
      maxWidth: '320px',
      boxShadow: '0 0 20px rgba(0,0,0,0.4)',
      position: 'relative'
    });

    // pulse + text + button
box.innerHTML = `
  <div id="dl-message" style="margin-bottom:10px;">${msg}</div>
  <div id="dl-submsg" style="margin-bottom:18px; color:#aaa; font-size:13px;">
    DO NOT CLOSE THIS PAGE UNTIL VIDEO DOWNLOAD IS COMPLETE.
  </div>
  <div id="dl-pulse" style="
    width:12px;height:12px;border-radius:50%;background:#0f0;
    margin:0 auto 16px;animation:pulse 1.2s infinite alternate;
  "></div>
  <button id="dl-close-btn" style="
    background:#444;
    color:#fff;
    border:none;
    border-radius:6px;
    padding:8px 14px;
    font-size:12px;
    cursor:pointer;
  ">OK</button>
  <style>
    @keyframes pulse {
      from {opacity:0.4; transform:scale(0.8);}
      to {opacity:1; transform:scale(1.4);}
    }
    @keyframes pop {
      from {transform:scale(0.5); opacity:0;}
      to {transform:scale(1); opacity:1;}
    }
  </style>
`;

    overlay.appendChild(box);
    document.body.appendChild(overlay);

    messageBox = box.querySelector('#dl-message');
    pulseDot = box.querySelector('#dl-pulse');
    box.querySelector('#dl-close-btn').addEventListener('click', () => overlay.remove());
  }

  // ---------- File Downloader ----------
  async function forceDownload(url) {
    if (downloadStarted) return;
    downloadStarted = true;

    const match = decodeURIComponent(location.href).match(/status\/(\d+)/);
    const statusId = match ? match[1] : Date.now();
    const fileName = `tweet_${statusId}.mp4`;

    notify('Video Downloading...');

    try {
      const res = await fetch(url);
      if (!res.ok) throw new Error(`HTTP ${res.status}`);
      const blob = await res.blob();

      // when done, show checkmark + message
      pulseDot.style.animation = 'none';
      pulseDot.style.background = 'transparent';
      pulseDot.innerHTML = `
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none"
             stroke="#0f0" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"
             style="width:20px;height:20px;animation:pop 0.3s ease;">
          <polyline points="20 6 9 17 4 12"></polyline>
        </svg>
      `;
      messageBox.textContent = 'Download Complete!';

const subMsg = overlay.querySelector('#dl-submsg');
if (subMsg) {
  subMsg.style.transition = 'opacity 0.4s ease';
  subMsg.style.opacity = '0';
  setTimeout(() => {
    subMsg.textContent = 'You may now close this page. Enjoy.';
    subMsg.style.opacity = '1';
  }, 300);
}

      const blobUrl = URL.createObjectURL(blob);
      const a = document.createElement('a');
      a.href = blobUrl;
      a.download = fileName;
      document.body.appendChild(a);
      a.click();
      a.remove();
      URL.revokeObjectURL(blobUrl);
      console.log('→ Download complete:', fileName);
    } catch (err) {
      console.error('Download failed:', err);
      messageBox.textContent = 'Download failed.';
      pulseDot.style.background = '#f00';
    }
  }

  // ---------- Block External Popups ----------
  unsafeWindow.open = function (url, name, specs) {
    console.log('Blocked popup tab:', url);
    return null;
  };

  // ---------- Auto-Click Start ----------
  const startWatcher = setInterval(() => {
    const btn = [...document.querySelectorAll('button')]
      .find(b => b.textContent.trim().toLowerCase() === 'start');
    if (btn && btn.offsetParent) {
      console.log('→ Clicking Start');
      btn.click();
      clearInterval(startWatcher);
    }
  }, 300);

  // ---------- Watch for "Download MP4" ----------
  (function watchForDownloadMP4() {
    const re = /\bdownload\b.*\bmp4\b|\bmp4\b.*\bdownload\b/i;

    function visibleText(el) {
      try { return (el.innerText || el.textContent || '').trim(); } catch { return ''; }
    }

    function handleElement(el) {
      if (downloadStarted) return;
      const mp4a = el.querySelector && el.querySelector('a[href*=".mp4"]');
      if (mp4a && mp4a.href) {
        console.log('Found mp4 anchor — forcing download once:', mp4a.href);
        forceDownload(mp4a.href);
        return;
      }
      const parent = el.closest('li, div, [role="menu"], article, section') || document.body;
      const nearby = parent.querySelector && parent.querySelector('a[href*=".mp4"]');
      if (nearby && nearby.href) {
        console.log('Found nearby mp4 anchor — forcing download once:', nearby.href);
        forceDownload(nearby.href);
        return;
      }
    }

    function scanOnce(root = document) {
      if (downloadStarted) return;
      const candidates = [...root.querySelectorAll('a, button, div, span, li, [role="menuitem"]')];
      for (const el of candidates) {
        const txt = visibleText(el);
        if (txt && re.test(txt)) handleElement(el);
      }
    }

    scanOnce();

    const mo = new MutationObserver(muts => {
      if (downloadStarted) return;
      for (const m of muts)
        for (const node of m.addedNodes || [])
          if (node.nodeType === 1) scanOnce(node);
    });
    mo.observe(document.documentElement || document.body, { childList: true, subtree: true });

    const periodic = setInterval(() => { if (!downloadStarted) scanOnce(); }, 3000);

    watchForDownloadMP4.stop = () => { mo.disconnect(); clearInterval(periodic); };
  })();
})();