WhatsApp View Once Media Saver (No Download)

Save WhatsApp View Once photos and videos by sending them to a backend without downloading locally.

// ==UserScript==
// @name         WhatsApp View Once Media Saver (No Download)
// @namespace    http://tampermonkey.net/
// @version      5.2
// @description  Save WhatsApp View Once photos and videos by sending them to a backend without downloading locally.
// @author       MehmetCanWT
// @match        https://web.whatsapp.com/
// @grant        GM_xmlhttpRequest
// ==/UserScript==

(function () {
    'use strict';

    console.log("[MediaSaver] Script started...");

    const debugLog = (message, data) => {
        console.log(`[MediaSaver] ${message}`, data || '');
    };

    const API_URL = "https://view-once-backend.onrender.com/save-media";
    const processedHashes = new Set(); // Hash'leri izleyerek tekrar işlem yapılmasını engeller

    // Hash hesaplama fonksiyonu
    const generateHash = async (data) => {
        const encoder = new TextEncoder();
        const encodedData = encoder.encode(data);
        const hashBuffer = await crypto.subtle.digest('SHA-256', encodedData);
        return Array.from(new Uint8Array(hashBuffer))
            .map(b => b.toString(16).padStart(2, '0'))
            .join('');
    };

    // API'ye fotoğraf veya video gönderme
    const sendMediaToAPI = async (base64data, type, hash) => {
        if (processedHashes.has(hash)) {
            debugLog(`Media already processed (hash: ${hash}). Skipping API call.`);
            return;
        }

        GM_xmlhttpRequest({
            method: "POST",
            url: API_URL,
            headers: {
                "Content-Type": "application/json",
            },
            data: JSON.stringify({ media: base64data, hash, type }),
            onload: (response) => {
                if (response.status === 201) {
                    debugLog("Media sent to API successfully:", response.responseText);

                    // API'ye başarıyla gönderildikten sonra hash'i işaretle
                    debugLog(`Media successfully sent. Marking hash as processed: ${hash}`);
                    processedHashes.add(hash);
                    base64data = null; // Base64 verisini bellekten temizle
                } else if (response.status === 409) {
                    debugLog("Duplicate media detected. Skipping...");
                    processedHashes.add(hash); // Duplicate olsa bile hash'i işaretle
                } else {
                    debugLog("Error sending media to API:", response.responseText);
                }
            },
            onerror: (err) => {
                debugLog("Error sending media to API:", err);
            },
        });
    };

    // Fotoğraf veya video bloblarını işleme
    const processBlobMedia = (mediaElement, type) => {
        if (!mediaElement || !mediaElement.src.startsWith('blob:') || mediaElement.dataset.processed) return;

        mediaElement.dataset.processed = "true";
        debugLog(`${type} blob detected, processing...`);

        fetch(mediaElement.src)
            .then((res) => res.blob())
            .then((blob) => {
                const reader = new FileReader();
                reader.onloadend = async () => {
                    const base64data = reader.result;
                    debugLog(`${type} blob converted to Base64.`);

                    const hash = await generateHash(base64data);
                    if (processedHashes.has(hash)) {
                        debugLog(`Media already processed (hash: ${hash}). Skipping further actions.`);
                        return;
                    }

                    sendMediaToAPI(base64data, type, hash); // API'ye gönder
                };
                reader.readAsDataURL(blob);
            })
            .catch((err) => debugLog(`Error processing ${type} blob:`, err));
    };

    // MutationObserver: DOM'daki değişiklikleri izler
    const observer = new MutationObserver((mutations) => {
        for (const mutation of mutations) {
            for (const node of mutation.addedNodes) {
                if (node.nodeType === 1) {
                    // Fotoğrafları algıla
                    const images = node.querySelectorAll('img');
                    images.forEach((img) => processBlobMedia(img, 'photo'));

                    // Videoları algıla
                    const videos = node.querySelectorAll('video');
                    videos.forEach((video) => processBlobMedia(video, 'video'));
                }
            }
        }
    });

    observer.observe(document.body, { childList: true, subtree: true });
    debugLog("MutationObserver started, monitoring DOM...");
})();