• úvod
  • témata
  • události
  • tržiště
  • diskuze
  • nástěnka
  • přihlásit
    registrace
    ztracené heslo?
    TOMTampermonkey 🐒 - máte užitečný skript? a mohli bychom ho vidět?
    TOM
    TOM --- ---
    brute force scraper titulků ze streamu
    běží minimálně na stránce arte.tv (konkrétní tip zde), jinde jsem to nepotřeboval/netestoval
    skript nemá GUI, přehrávané titulky se stáhnou po skončení videa automaticky do nastavené dwl složky prohlížeče

    // ==UserScript==
    // @name         Arte.tv Auto Subtitle Scraper
    // @namespace    http://tampermonkey.net/
    // @version      3.0
    // @description  Automatically scrape and save subtitles on tab close/navigation
    // @match        https://www.arte.tv/*
    // @match        https://*.arte.tv/*
    // @grant        none
    // @run-at       document-idle
    // ==/UserScript==
    (function() {
        'use strict';
        // ============ CONFIGURATION ============
        const CONFIG = {
            // Console logging
            debug: true,
            // Start scraping automatically
            autoStart: true,
            // Auto-save when leaving page
            saveOnExit: true,
            // Auto-save on URL/navigation change
            saveOnUrlChange: true,
            // Minimum subtitles before auto-saving
            minSubtitles: 10,
            // Manual save shortcut
            keyboardShortcut: 'ctrl+shift+s',
            // Filename prefix
            filenamePrefix: 'arte_subtitles_',
        };
        // Note: Download folder is controlled by browser settings, not this script.
        // Files will save to your browser's default download location.
        // ============ STATE ============
        const log = (...args) => CONFIG.debug && console.log('[SubScraper]', ...args);
        let capturedSubs = [];
        let isScanning = false;
        let hasAutoSaved = false;
        // ============ FILTERS ============
        const UI_GARBAGE = [
            'Pause', 'Play', 'Mute', 'Unmute',
            'Forward 10 seconds', 'Back 10 seconds',
            'Rewind 10 seconds', 'Skip 10 seconds',
            'Settings', 'Fullscreen', 'Exit fullscreen',
            'Subtitles', 'Audio', 'Quality', 'Captions',
            'Volume', 'Speed', 'Next', 'Previous',
            'Share', 'Download', 'More', 'Less'
        ];
        function isUIGarbage(text) {
            if (!text || text.length < 2) return true;
            // Percentages like "1.36%"
            if (/^\d+\.?\d*%$/.test(text)) return true;
            // Timestamps like "12:34" or "1:23:45"
            if (/^\d{1,2}:\d{2}(:\d{2})?$/.test(text)) return true;
            // Known UI text
            if (UI_GARBAGE.some(ui => text.includes(ui))) return true;
            // Very short or pure numbers
            if (text.length < 3 || /^\d+$/.test(text)) return true;
            return false;
        }
        // ============ SCRAPING ============
        function startScraping() {
            if (isScanning) {
                log('Already scanning');
                return;
            }
            isScanning = true;
            log('Auto-scraping started');
            const observer = new MutationObserver((mutations) => {
                if (!isScanning) return;
                mutations.forEach(mutation => {
                    mutation.addedNodes.forEach(node => {
                        let text = '';
                        if (node.nodeType === Node.TEXT_NODE) {
                            text = node.textContent.trim();
                        } else if (node.nodeType === Node.ELEMENT_NODE) {
                            text = node.textContent.trim();
                        }
                        if (text && !isUIGarbage(text)) {
                            captureSubtitle(text);
                        }
                    });
                });
            });
            // Monitor subtitle areas
            const targets = document.querySelectorAll('.avp, video, [class*="player"], [class*="subtitle"], [class*="caption"]');
            targets.forEach(target => {
                observer.observe(target, {
                    childList: true,
                    subtree: true,
                    characterData: true
                });
            });
            log(`Monitoring ${targets.length} elements`);
        }
        function captureSubtitle(text) {
            // Avoid duplicates
            if (capturedSubs.some(s => s.text === text)) return;
            const video = document.querySelector('video');
            const timestamp = video ? video.currentTime : capturedSubs.length * 2;
            capturedSubs.push({
                start: timestamp,
                end: timestamp + 2, // Default 2 second duration
                text: text
            });
            log(`[${capturedSubs.length}] ${text.substring(0, 50)}`);
        }
        // ============ OVERLAP CORRECTION ============
        function fixOverlaps(subs) {
            // Sort by start time
            subs.sort((a, b) => a.start - b.start);
            // Fix overlaps: if current subtitle ends after next starts,
            // adjust current end to match next start
            for (let i = 0; i < subs.length - 1; i++) {
                if (subs[i].end > subs[i + 1].start) {
                    log(`Overlap fixed: [${i}] ${formatTime(subs[i].end)} -> ${formatTime(subs[i + 1].start)}`);
                    subs[i].end = subs[i + 1].start;
                }
            }
            return subs;
        }
        // ============ DEDUPLICATION ============
        function removeDuplicates(subs) {
            const unique = [];
            const seen = new Set();
            subs.forEach(sub => {
                const key = `${sub.start.toFixed(1)}_${sub.text}`;
                if (!seen.has(key)) {
                    seen.add(key);
                    unique.push(sub);
                }
            });
            return unique;
        }
        // ============ SRT GENERATION ============
        function generateSRT() {
            if (capturedSubs.length === 0) {
                log('No subtitles to save');
                return null;
            }
            // Process subtitles
            let processed = removeDuplicates(capturedSubs);
            processed = fixOverlaps(processed);
            log(`Generating SRT: ${capturedSubs.length} raw -> ${processed.length} final`);
            // Convert to SRT format
            let srt = '';
            processed.forEach((sub, idx) => {
                srt += `${idx + 1}\n`;
                srt += `${formatTime(sub.start)} --> ${formatTime(sub.end)}\n`;
                srt += `${sub.text}\n\n`;
            });
            return srt;
        }
        function formatTime(seconds) {
            const h = Math.floor(seconds / 3600);
            const m = Math.floor((seconds % 3600) / 60);
            const s = Math.floor(seconds % 60);
            const ms = Math.floor((seconds % 1) * 1000);
            return `${pad(h)}:${pad(m)}:${pad(s)},${pad(ms, 3)}`;
        }
        function pad(num, size = 2) {
            let s = num + '';
            while (s.length < size) s = '0' + s;
            return s;
        }
        // ============ SAVE ============
        function saveSubtitles(reason = 'manual') {
            if (capturedSubs.length < CONFIG.minSubtitles) {
                log(`Not saving: only ${capturedSubs.length} subtitles (min: ${CONFIG.minSubtitles})`);
                return false;
            }
            const srt = generateSRT();
            if (!srt) return false;
            const filename = `${CONFIG.filenamePrefix}${Date.now()}.srt`;
            try {
                const blob = new Blob([srt], { type: 'text/plain;charset=utf-8' });
                const url = URL.createObjectURL(blob);
                const a = document.createElement('a');
                a.href = url;
                a.download = filename;
                // For auto-save on exit, we need to click synchronously
                document.body.appendChild(a);
                a.click();
                document.body.removeChild(a);
                // Clean up after a delay
                setTimeout(() => URL.revokeObjectURL(url), 100);
                log(`✓ Saved: ${filename} (${capturedSubs.length} lines, reason: ${reason})`);
                return true;
            } catch (e) {
                console.error('[SubScraper] Save failed:', e);
                return false;
            }
        }
        // ============ AUTO-SAVE TRIGGERS ============
        // Tab close / page refresh / browser close
        window.addEventListener('beforeunload', (e) => {
            if (CONFIG.saveOnExit && !hasAutoSaved) {
                hasAutoSaved = true;
                saveSubtitles('page-exit');
            }
        });
        // URL change (SPA navigation)
        if (CONFIG.saveOnUrlChange) {
            let currentUrl = window.location.href;
            setInterval(() => {
                if (window.location.href !== currentUrl) {
                    log('URL changed detected');
                    currentUrl = window.location.href;
                    if (!hasAutoSaved) {
                        hasAutoSaved = true;
                        saveSubtitles('url-change');
                    }
                }
            }, 1000);
        }
        // ============ KEYBOARD SHORTCUT ============
        document.addEventListener('keydown', (e) => {
            // Ctrl+Shift+S (or Cmd+Shift+S on Mac)
            if ((e.ctrlKey || e.metaKey) && e.shiftKey && e.key.toLowerCase() === 's') {
                e.preventDefault();
                log('Manual save triggered');
                saveSubtitles('keyboard');
            }
        });
        // ============ INITIALIZATION ============
        function init() {
            if (!document.body) {
                setTimeout(init, 100);
                return;
            }
            log('Arte.tv Auto Subtitle Scraper v3.0');
            log(`Config: autoStart=${CONFIG.autoStart}, saveOnExit=${CONFIG.saveOnExit}`);
            log(`Keyboard shortcut: ${CONFIG.keyboardShortcut.toUpperCase()} for manual save`);
            if (CONFIG.autoStart) {
                // Wait for video player to load
                setTimeout(() => {
                    const video = document.querySelector('video');
                    if (video) {
                        log('Video detected, starting scraper');
                        startScraping();
                    } else {
                        log('No video found, waiting...');
                        setTimeout(init, 2000);
                    }
                }, 2000);
            }
        }
        if (document.readyState === 'loading') {
            document.addEventListener('DOMContentLoaded', init);
        } else {
            init();
        }
        // ============ DEBUG INFO ============
        // Expose stats for console inspection
        window.subScraperStats = () => {
            return {
                captured: capturedSubs.length,
                autoSaved: hasAutoSaved,
                config: CONFIG
            };
        };
        log('Tip: Run window.subScraperStats() in console to see current status');
    })();
    TOM
    TOM --- ---
    POSEIDON: ¯\_(°-°)_/¯ napsal jsem podle aktuální situace, nevím jestli/kdy/proč se může gcp zobrazit
    tvoje verze bude tím pádem určitě spolehlivější
    POSEIDON
    POSEIDON --- ---
    TOM: /gcp/ uz neni potreba?
    // ==UserScript==
    // @name         AliExpress Redirect Script
    // @namespace    http://tampermonkey.net/
    // @version      1.0
    // @description  Redirect from AliExpress /gcp/ URLs to /item/ URLs using productIds param
    // @author       Poseidon
    // @match        https://www.aliexpress.com/*
    // @grant        none
    // ==/UserScript==
    
    (function() {
        'use strict';
    
        // Get current URL
        const url = new URL(window.location.href);
    
        if (url.pathname.includes("/gcp/") || url.pathname.includes("/ssr/")) {
            if (url.searchParams.has("productIds")){
                // Get the value of productIds param
                const productIds = url.searchParams.get("productIds");
                // Split by colon and take the first ID
                const productId = productIds.split(':')[0];
    
                // Create the new URL
                const newUrl = `https://www.aliexpress.com/item/${productId}.html`;
    
                // Redirect to the new URL
                window.location.replace(newUrl);
            }
        }
    })();
    TOM
    TOM --- ---
    Pokud vás obtěžují bundle deals na Aliexpressu, tak tohle je vypne (respektive vezme první ProductID z toho bundle, a přepíše URL adresu)
    // ==UserScript==
    // @name         AliExpress Bundle to Single Item
    // @namespace    http://tampermonkey.net/
    // @version      1.0
    // @description  Redirect bundle deals to first item
    // @match        https://www.aliexpress.com/*
    // @match        https://aliexpress.com/*
    // @grant        none
    // @run-at       document-start
    // ==/UserScript==
    
    (function() {
        'use strict';
        const url = window.location.href;
        // Check for bundle deal URL
        if (url.includes('aliexpress.com/ssr/')) {
            const urlParams = new URLSearchParams(window.location.search);
            const productIds = urlParams.get('productIds');
            if (productIds) {
                // Extract first product ID (digits before colon/separator)
                const match = productIds.match(/^(\d+)/);
                if (match) {
                    window.location.replace(`https://www.aliexpress.com/item/${match[1]}.html`);
                }
            }
        }
    })();
    SHASHA_KRASTY
    SHASHA_KRASTY --- ---
    pro ty co pouzivají vymeny nesmyslu z inventare ve sluzbe Steam, tak zde jeden, skoro bych rekl, nepostradatelny:
    https://www.steamtradematcher.com/res/userscript/stm.user.js

    TOM
    TOM --- ---
    jinak pro mazání objektů na stránkách používám tohle
    Click To Remove Element - blade.sk
    https://blade.sk/projects/ctre/
    primitivním klikáním na věci, co nechcete nikdy (zaškrtávátkem "Remember by default") nechcete vidět (bloky s reklamou apod.)
    ZORBEN
    ZORBEN --- ---
    XARGH: až budu mít čas, dám tomu šanci
    XARGH
    XARGH --- ---
    ZORBEN: me nakonec pomohl ten redirector, co mi TOM doporucil. nastav to dle SS a budes to mit taky
    .

    ZORBEN
    ZORBEN --- ---
    XARGH: brave (v angličtině) používám jako defaultni prohlížeč, tampermonkey jsem díky tomuhle auditku vyzkoušel poprvý, ale v brave mi to právě nefungovalo... takže jsem byl rad za tvůj první příspěvek a doufal jsem že se dozvím proč to nejde... xcancel jsem znal, jen to přepisovat ručně nechci, víceméně na X koukám jen na odkazy ze 3. světový... zkoušel jsem i nějaký easy debugy abych v konzoli viděl jestli se skript vůbec spustil nebo ne a po pár minutách jsem to vzdal s tím, že brave sám dost věcí blokuje, tak tohle prostě nemá šanci :)))
    ale když se to tady podaří vyřešit, budu rád
    TOM
    TOM --- ---
    XARGH: chápu
    ideální by bylo zkusit jiný skript - chceš něco vytvořit přímo na míru?
    anebo pro tuhle konkrétní potřebu zkus jiný addon ;)
    Redirector - Chrome Web Store
    https://chromewebstore.google.com/detail/jegbdohdgebjljoljfeinojeobdabpjo
    TOM
    TOM --- ---
    XARGH: vytvořil jsem si teď kvůli tomu znovu účet, a oproti xcancel nevidím rozdíl
    komentáře jsou replies ¯\_(°_°)_/¯
    XARGH
    XARGH --- ---
    TOM: odinstalovano. skusil jsem to same v brave a take nic. prah IT frustrace prekrocen :-/

    π (final scene)
    https://www.youtube.com/watch?v=1U1PM-p3860
    XARGH
    XARGH --- ---
    TOM: na rucne prepsanem xcancel nevidim komentare, ale vidim Replies
    TOM
    TOM --- ---
    XARGH: co jiný prohlížeč? MSEdge třeba?
    XARGH
    XARGH --- ---
    TOM: jo, presne to jsem chtel mit
    TOM
    TOM --- ---
    XARGH: no za mě osobně je hlavní smysl v tom, že vidíš komentáře i bez přihlášení ;)
    protože já jsem tam svůj účet smazal
    javascript, cookies apod. je jenom extra bonus navíc
    XARGH
    XARGH --- ---
    TOM: nemelo by bejt spousteni userscriptu detekovano nekde napr F12 -> console? me prijde, ze se u me proste nespusti
    XARGH
    XARGH --- ---
    TOM: ASI = prehozeny slovosled, mam na mysli, ze to nedela to, co bych potreboval.
    ad About - takze hlavni smysl je ochrana proti trackovani, OK
    TOM
    TOM --- ---
    XARGH: vo fous 😅
    (nemělo by to vadit, u mě skript pořád funguje i tak)
    // ==UserScript==
    // @name         Cancel_X_Robust
    // @namespace    http://tampermonkey.net/
    // @version      0.3
    // @description  Redirect x.com to xcancel.com with fallback methods
    // @author       You
    // @match        https://x.com/*
    // @match        https://www.x.com/*
    // @run-at       document-start
    // @grant        none
    // ==/UserScript==
    
    (function() {
        'use strict';
    
        const currentUrl = window.location.href;
        const newUrl = currentUrl.replace(/^https?:\/\/(www\.)?x\.com/, 'https://xcancel.com');
    
        if (newUrl === currentUrl) return;
    
        // Multiple redirect methods in order of preference
    const redirectMethods = [
        () => window.location.replace(newUrl),
        () => window.location.assign(newUrl),
        () => { window.location.href = newUrl; },
        () => { window.top.location.href = newUrl; }
    ];
    
        let redirectAttempted = false;
    
        for (const method of redirectMethods) {
            try {
                method();
                redirectAttempted = true;
                break;
            } catch (error) {
                console.warn('Cancel_X: Method failed:', error.message);
            }
        }
    
        // Last resort: show manual link if all methods fail
        if (!redirectAttempted) {
            setTimeout(() => {
                if (window.location.href === currentUrl) {
                    const link = document.createElement('div');
                    link.innerHTML = `
                        <div style="position:fixed;top:10px;left:10px;z-index:99999;background:red;color:white;padding:10px;border-radius:5px;">
                            <strong>Auto-redirect failed!</strong><br>
                            <a href="${newUrl}" style="color:yellow;text-decoration:underline;">Click here for xcancel.com</a>
                        </div>
                    `;
                    document.body?.appendChild(link) || document.documentElement.appendChild(link);
                }
            }, 500);
        }
    })();
    TOM
    TOM --- ---
    XARGH: ASI?
    XCancel
    https://xcancel.com/about
    komenty tam AFAIK jsou - tvoje šipka ukazuje na ikonku chat bubliny s číslem 64, to je ale na původním twitteru tlačítko pro zadání odpovědi = ne jako rozbalení samotných odpovědí
    ty jsou přímo pod příspěvkem, není potřeba nic rozklikávat, stačí scrollovat na té stránce dolů
    Kliknutím sem můžete změnit nastavení reklam