Remove & perma block instagram reels from Facebook

Automatically skip URLs that have been previously skipped and click "Next" if Instagram content is detected on Facebook Reels. Displays total skipped reels count in a popup and allows exporting skipped URLs to a text file using IndexedDB for storage.

// ==UserScript==
// @name         Remove & perma block instagram reels from Facebook
// @namespace    IG GONE
// @version      1.2
// @description  Automatically skip URLs that have been previously skipped and click "Next" if Instagram content is detected on Facebook Reels. Displays total skipped reels count in a popup and allows exporting skipped URLs to a text file using IndexedDB for storage.
// @author       Projekt Darkside - Brno city Bohemia
// @match        https://www.facebook.com/*
// @grant        none
// ==/UserScript==

(async function() {
    'use strict';

    const reelDurationMinutes = 2;
    let db;

    // Vícejazyčná podpora pro tlačítko "Další karta"
    const multilangNextButton = {
        'en': 'Next card',
        'cs': 'Další karta',
        'sk': 'Ďalšia karta',
        'fr': 'Carte suivante',
        'es': 'Siguiente tarjeta',
        'de': 'Nächste Karte',
        'it': 'Prossima carta',
        'pt': 'Próximo cartão',
        'ru': 'Следующая карта',
        'ja': '次のカード',
    };

    // Vícejazyčná podpora pro text v badge
    const multilangBadgeText = {
        'en': 'Skipped (count) IG reels, this script saved you (time) minutes.',
        'cs': 'Přeskočeno (count) IG reelů, tento skript vám ušetřil (time) minut.',
        'sk': 'Preskočených (count) IG reelov, tento skript vám ušetril (time) minút.',
        'fr': '(count) IG reels ignorés, ce script vous a fait gagner (time) minutes.',
        'es': 'Saltado (count) IG reels, este script te ahorró (time) minutos.',
        'de': '(count) IG-Reels übersprungen, dieses Skript hat Ihnen (time) Minuten gespart.',
        'it': 'Saltato (count) IG reels, questo script ti ha fatto risparmiare (time) minuti.',
        'pt': 'Ignorado (count) IG reels, este script te poupou (time) minutos.',
        'ru': 'Пропущено (count) IG рилов, этот скрипт сэкономил вам (time) минут.',
        'ja': 'スキップされた (count) IGリール、このスクリプトはあなたの時間を (time) 分節約しました。',
    };

    async function openDatabase() {
        return new Promise((resolve, reject) => {
            const request = indexedDB.open('InstagramReelsDB', 1);

            request.onupgradeneeded = function(event) {
                db = event.target.result;
                if (!db.objectStoreNames.contains('skippedURLs')) {
                    db.createObjectStore('skippedURLs', { keyPath: 'url' });
                }
            };

            request.onsuccess = function(event) {
                db = event.target.result;
                resolve(db);
            };

            request.onerror = function(event) {
                reject('Error opening database: ' + event.target.errorCode);
            };
        });
    }

    async function saveSkippedURL(url) {
        return new Promise((resolve, reject) => {
            const transaction = db.transaction(['skippedURLs'], 'readwrite');
            const objectStore = transaction.objectStore('skippedURLs');
            const request = objectStore.add({ url: url });

            request.onsuccess = function(event) {
                updateSkippedCountBadge();
                resolve(event.target.result);
            };

            request.onerror = function(event) {
                if (event.target.error.name === 'ConstraintError') {
                    resolve(false); // URL již existuje
                } else {
                    reject('Error saving URL: ' + event.target.error);
                }
            };
        });
    }

    async function getSkippedURLs() {
        return new Promise((resolve, reject) => {
            const transaction = db.transaction(['skippedURLs']);
            const objectStore = transaction.objectStore('skippedURLs');
            const request = objectStore.getAll();

            request.onsuccess = function(event) {
                resolve(event.target.result);
            };

            request.onerror = function(event) {
                reject('Error getting URLs: ' + event.target.error);
            };
        });
    }

    async function isURLSkipped(url) {
        return new Promise((resolve, reject) => {
            const transaction = db.transaction(['skippedURLs']);
            const objectStore = transaction.objectStore('skippedURLs');
            const request = objectStore.get(url);

            request.onsuccess = function(event) {
                resolve(!!event.target.result);
            };

            request.onerror = function(event) {
                reject('Error checking URL: ' + event.target.error);
            };
        });
    }

    function getBadgeText(count, time) {
        let language = detectBrowserLanguage().split('-')[0];
        let template = multilangBadgeText[language] || multilangBadgeText['en'];
        return template.replace('(count)', count).replace('(time)', time);
    }

    async function updateSkippedCountBadge() {
        let skippedURLs = await getSkippedURLs();
        let count = skippedURLs.length;
        let savedTime = count * reelDurationMinutes;
        let badge = document.getElementById('skippedCountBadge');
        if (!badge) {
            badge = document.createElement('div');
            badge.id = 'skippedCountBadge';
            badge.style.cssText = 'position: fixed; bottom: 10px; left: 10px; background-color: #007bff; color: #fff; border-radius: 10px; padding: 10px; cursor: pointer; z-index: 9999;';
            badge.title = 'Celkový počet přeskočených Instagram Reels a ušetřený čas';
            badge.onclick = exportSkippedURLs;
            document.body.appendChild(badge);
        }
        badge.textContent = getBadgeText(count, savedTime);
    }

    function showAndAutoHideBadge() {
        let badge = document.getElementById('skippedCountBadge');
        if (badge) {
            badge.style.display = 'block';
            setTimeout(() => {
                badge.style.display = 'none';
            }, 4000);
        }
    }

    async function exportSkippedURLs() {
        let skippedURLs = await getSkippedURLs();
        let urls = skippedURLs.map(item => item.url);
        let blob = new Blob([urls.join('\n')], { type: 'text/plain' });
        let url = URL.createObjectURL(blob);
        let a = document.createElement('a');
        a.style.display = 'none';
        a.href = url;
        a.download = 'skipped_urls.txt';
        document.body.appendChild(a);
        a.click();
        window.URL.revokeObjectURL(url);
    }

    async function skipInstagramContent() {
        if (window.location.href.includes("/reel/")) {
            let isSkipped = await isURLSkipped(window.location.href);
            if (isSkipped) {
                let nextButton = findNextButton();
                if (nextButton) {
                    nextButton.click();
                }
                return;
            }

            let foundInstagramContent = false;

            document.querySelectorAll('*').forEach(function(node) {
                if (node.offsetParent !== null && node.innerText && node.innerText.includes("Instagram")) {
                    foundInstagramContent = true;
                }
            });

            document.querySelectorAll('img').forEach(function(img) {
                if (img.offsetParent !== null && img.src.includes('https://static.xx.fbcdn.net/rsrc.php/v3/yy/r/1M3EBv90kJA.png')) {
                    foundInstagramContent = true;
                }
            });

            if (foundInstagramContent) {
                let nextButton = findNextButton();
                if (nextButton) {
                    await saveSkippedURL(window.location.href);
                    nextButton.click();
                }
            }
        }
    }

    function findNextButton() {
        let language = detectBrowserLanguage().split('-')[0];
        let ariaLabel = multilangNextButton[language];
        if (ariaLabel) {
            return document.querySelector(`[aria-label="${ariaLabel}"]`);
        } else {
            return null;
        }
    }

    function detectBrowserLanguage() {
        return navigator.language || navigator.userLanguage || 'en';
    }

    window.addEventListener('load', async () => {
        await openDatabase();
        await updateSkippedCountBadge();
    });

    window.addEventListener('beforeunload', async () => {
        await updateSkippedCountBadge();
    });

    const observer = new MutationObserver(skipInstagramContent);
    observer.observe(document.body, { childList: true, subtree: true });

    skipInstagramContent();
})();