Tampermonkey Video Filter v4

Filters posts with videos using dynamic content detection, improved performance.

As of 2025-08-11. See the latest version.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name          Tampermonkey Video Filter v4
// @namespace    http://tampermonkey.net/
// @version      1.1.2
// @description  Filters posts with videos using dynamic content detection, improved performance.
// @author       harryangstrom, xdegeneratex, remuru, AI Assistant
// @match        https://*.coomer.party/*
// @match        https://*.coomer.su/*
// @match        https://*.coomer.st/*
// @match        https://*.kemono.su/*
// @match        https://*.kemono.party/*
// @match        https://*.kemono.cr/*
// @grant        GM_setClipboard
// @grant        GM_xmlhttpRequest
// @run-at       document-idle
// @license MIT
// ==/UserScript==

(function() {
    'use strict';

    const VIDEO_EXTENSIONS = ['mp4', 'm4v', 'mov'];
    const IMAGE_EXTENSIONS = ['jpg', 'jpeg', 'png', 'gif'];
    const POSTS_PER_PAGE = 50;
    const API_DELAY = 2000;
    const SUBSTRING_TITLE_LENGTH = 100;
    const LS_COLLAPSE_KEY = 'videoFilterPanelCollapsed_v1';

    let currentDomain = window.location.hostname;
    let allFoundVideoUrls = [];
    let videoIntersectionObserver = null;
    let isPanelCollapsed = false;

    // ... (UI elements creation: uiContainer, collapseButton, panelMainContent, pageRangeInput, filterButton, copyUrlsButton, statusMessage - REMAINS THE SAME) ...
    const uiContainer = document.createElement('div');
    uiContainer.id = 'video-filter-ui';
    uiContainer.style.position = 'fixed';
    uiContainer.style.bottom = '10px';
    uiContainer.style.right = '10px';
    uiContainer.style.backgroundColor = '#2c2c2e';
    uiContainer.style.color = '#e0e0e0';
    uiContainer.style.border = '1px solid #444444';
    const initialUiContainerPadding = '12px';
    uiContainer.style.padding = initialUiContainerPadding;
    uiContainer.style.zIndex = '9999';
    uiContainer.style.fontFamily = 'Arial, sans-serif';
    uiContainer.style.fontSize = '14px';
    uiContainer.style.boxShadow = '0 2px 8px rgba(0,0,0,0.5)';
    uiContainer.style.borderRadius = '4px';
    uiContainer.style.transition = 'width 0.2s ease-in-out, height 0.2s ease-in-out, padding 0.2s ease-in-out';

    const collapseButton = document.createElement('button');
    collapseButton.id = 'video-filter-collapse-button';
    collapseButton.innerHTML = '»';
    collapseButton.title = 'Collapse/Expand Panel';
    collapseButton.style.position = 'absolute';
    collapseButton.style.bottom = '8px';
    collapseButton.style.left = '8px';
    collapseButton.style.width = '25px';
    collapseButton.style.height = '60px';
    collapseButton.style.display = 'flex';
    collapseButton.style.alignItems = 'center';
    collapseButton.style.justifyContent = 'center';
    collapseButton.style.padding = '0';
    collapseButton.style.fontSize = '16px';
    collapseButton.style.backgroundColor = '#4a4a4c';
    collapseButton.style.color = '#f0f0f0';
    collapseButton.style.border = '1px solid #555555';
    collapseButton.style.borderRadius = '3px';
    collapseButton.style.cursor = 'pointer';
    collapseButton.style.zIndex = '1';

    const panelMainContent = document.createElement('div');
    panelMainContent.id = 'video-filter-main-content';
    panelMainContent.style.marginLeft = '30px';

    const pageRangeInput = document.createElement('input');
    pageRangeInput.type = 'text';
    pageRangeInput.id = 'video-filter-page-range';
    pageRangeInput.value = '1';
    pageRangeInput.placeholder = 'e.g., 1, 2-5, 7';
    pageRangeInput.style.width = '100px';
    pageRangeInput.style.marginRight = '8px';
    pageRangeInput.style.padding = '6px 8px';
    pageRangeInput.style.backgroundColor = '#1e1e1e';
    pageRangeInput.style.color = '#e0e0e0';
    pageRangeInput.style.border = '1px solid #555555';
    pageRangeInput.style.borderRadius = '3px';

    const filterButton = document.createElement('button');
    filterButton.id = 'video-filter-button';
    filterButton.textContent = 'Filter Videos';

    const copyUrlsButton = document.createElement('button');
    copyUrlsButton.id = 'video-copy-urls-button';
    copyUrlsButton.textContent = 'Copy Video URLs';
    copyUrlsButton.disabled = true;

    const baseButtonBg = '#3a3a3c';
    const hoverButtonBg = '#4a4a4c';
    const disabledButtonBg = '#303030';
    const disabledButtonColor = '#777777';

    collapseButton.onmouseenter = () => { if (collapseButton.style.backgroundColor !== disabledButtonBg) collapseButton.style.backgroundColor = hoverButtonBg; };
    collapseButton.onmouseleave = () => { if (collapseButton.style.backgroundColor !== disabledButtonBg) collapseButton.style.backgroundColor = '#4a4a4c'; };

    function styleButton(button, disabled = false) {
        if (disabled) {
            button.style.backgroundColor = disabledButtonBg;
            button.style.color = disabledButtonColor;
            button.style.cursor = 'default';
        } else {
            button.style.backgroundColor = baseButtonBg;
            button.style.color = '#f0f0f0';
            button.style.cursor = 'pointer';
        }
        button.style.marginRight = '8px';
        button.style.padding = '6px 12px';
        button.style.border = '1px solid #555555';
        button.style.borderRadius = '3px';
    }

    [filterButton, copyUrlsButton].forEach(btn => {
        styleButton(btn, btn.disabled);
        btn.onmouseenter = () => { if (!btn.disabled) btn.style.backgroundColor = hoverButtonBg; };
        btn.onmouseleave = () => { if (!btn.disabled) btn.style.backgroundColor = baseButtonBg; };
    });

    const statusMessage = document.createElement('div');
    statusMessage.id = 'video-filter-status';
    statusMessage.style.marginTop = '8px';
    statusMessage.style.fontSize = '12px';
    statusMessage.style.minHeight = '15px';
    statusMessage.style.color = '#cccccc';

    panelMainContent.appendChild(document.createTextNode('Pages: '));
    panelMainContent.appendChild(pageRangeInput);
    panelMainContent.appendChild(filterButton);
    panelMainContent.appendChild(copyUrlsButton);
    panelMainContent.appendChild(statusMessage);

    uiContainer.appendChild(collapseButton);
    uiContainer.appendChild(panelMainContent);
    document.body.appendChild(uiContainer);


    // ... (functions: togglePanelCollapse, setupVideoIntersectionObserver, showStatus, parsePageRange - REMAINS THE SAME) ...
    function togglePanelCollapse() {
        isPanelCollapsed = !isPanelCollapsed;
        if (isPanelCollapsed) {
            panelMainContent.style.display = 'none';
            collapseButton.innerHTML = '«';
            uiContainer.style.width = '41px';
            uiContainer.style.height = '80px';
            uiContainer.style.padding = '0';
        } else {
            panelMainContent.style.display = 'block';
            collapseButton.innerHTML = '»';
            uiContainer.style.width = '';
            uiContainer.style.height = '';
            uiContainer.style.padding = initialUiContainerPadding;
        }
        localStorage.setItem(LS_COLLAPSE_KEY, isPanelCollapsed.toString());
    }
    collapseButton.addEventListener('click', togglePanelCollapse);

    const initiallyCollapsed = localStorage.getItem(LS_COLLAPSE_KEY) === 'true';
    if (initiallyCollapsed) {
        togglePanelCollapse();
    }

    function setupVideoIntersectionObserver() {
        if (videoIntersectionObserver) {
            videoIntersectionObserver.disconnect();
        }
        const options = { root: null, rootMargin: '200px 0px', threshold: 0.01 };
        videoIntersectionObserver = new IntersectionObserver((entries, observer) => {
            entries.forEach(entry => {
                if (entry.isIntersecting) {
                    const videoElement = entry.target;
                    const sourceElement = videoElement.querySelector('source[data-src]');
                    if (sourceElement) {
                        const videoUrl = sourceElement.getAttribute('data-src');
                        sourceElement.setAttribute('src', videoUrl);
                        videoElement.load();
                        sourceElement.removeAttribute('data-src');
                        observer.unobserve(videoElement);
                    }
                }
            });
        }, options);
    }

    function showStatus(message, type = 'info') {
        statusMessage.textContent = message;
        switch (type) {
            case 'error': statusMessage.style.color = '#ff6b6b'; break;
            case 'success': statusMessage.style.color = '#76c7c0'; break;
            case 'info': default: statusMessage.style.color = '#cccccc'; break;
        }
        if (type === 'success' && message.includes("Copied")) {
            setTimeout(() => {
                if (statusMessage.textContent === message) {
                    statusMessage.textContent = '';
                    statusMessage.style.color = '#cccccc';
                }
            }, 3000);
        }
    }

    function parsePageRange(inputStr) {
        const pages = new Set();
        if (!inputStr || inputStr.trim() === '') {
            showStatus('Error: Page range cannot be empty.', 'error');
            return null;
        }
        const parts = inputStr.split(',');
        for (const part of parts) {
            if (part.includes('-')) {
                const [startStr, endStr] = part.split('-');
                const start = parseInt(startStr, 10);
                const end = parseInt(endStr, 10);
                if (isNaN(start) || isNaN(end) || start <= 0 || end < start) {
                    showStatus(`Error: Invalid range "${part}". Start must be > 0 and end >= start.`, 'error');
                    return null;
                }
                for (let i = start; i <= end; i++) pages.add(i);
            } else {
                const page = parseInt(part, 10);
                if (isNaN(page) || page <= 0) {
                    showStatus(`Error: Invalid page number "${part}". Must be > 0.`, 'error');
                    return null;
                }
                pages.add(page);
            }
        }
        if (pages.size === 0) {
            showStatus('Error: No valid pages specified.', 'error');
            return null;
        }
        return Array.from(pages).sort((a, b) => a - b);
    }

    // ... (function: determinePageContext - REMAINS THE SAME from v1.2)
    function determinePageContext() {
        const pathname = window.location.pathname;
        const searchParams = new URLSearchParams(window.location.search);
        const query = searchParams.get('q');

        const userProfileMatch = pathname.match(/^\/([^/]+)\/user\/([^/]+)$/);

        if (userProfileMatch && !query) return { type: 'profile', service: userProfileMatch[1], userId: userProfileMatch[2] };
        if (userProfileMatch && query) return { type: 'user_search', service: userProfileMatch[1], userId: userProfileMatch[2], query };

        if (pathname === '/posts/popular') {
            let ctxDate = searchParams.get('date');
            let ctxPeriod = searchParams.get('period');
            let dateFound = !!ctxDate;
            let periodFound = !!ctxPeriod;

            if (!dateFound || !periodFound) {
                try {
                    const nextDataScript = document.getElementById('__NEXT_DATA__');
                    if (nextDataScript) {
                        const jsonData = JSON.parse(nextDataScript.textContent);
                        const pageProps = jsonData?.props?.pageProps;
                        if (pageProps) {
                            let tempDate = null, tempPeriod = null;
                            if (pageProps.data?.info?.date && pageProps.data?.base?.period) {
                                tempDate = pageProps.data.info.date.substring(0, 10);
                                tempPeriod = pageProps.data.base.period;
                            } else if (pageProps.props?.today && pageProps.props?.currentPage === "popular_posts") {
                                tempDate = pageProps.props.today;
                                tempPeriod = 'week';
                            } else if (pageProps.initialState?.feed?.feed?.info?.date && pageProps.initialState?.feed?.feed?.base?.period) {
                                tempDate = pageProps.initialState.feed.feed.info.date.substring(0, 10);
                                tempPeriod = pageProps.initialState.feed.feed.base.period;
                            } else if (pageProps.initialProps?.pageProps?.data?.info?.date && pageProps.initialProps?.pageProps?.data?.base?.period) {
                                tempDate = pageProps.initialProps.pageProps.data.info.date.substring(0, 10);
                                tempPeriod = pageProps.initialProps.pageProps.data.base.period;
                            }
                            if (!dateFound && tempDate) { ctxDate = tempDate; dateFound = true; }
                            if (!periodFound && tempPeriod) { ctxPeriod = tempPeriod; periodFound = true; }
                        }
                    }
                } catch (e) { console.warn("Video Filter: Could not parse __NEXT_DATA__.", e); }

                if (!dateFound) {
                    const popularDateSpan = document.querySelector('main#main.main section.site-section.site-section--popular-posts header.site-section__header h1.site-section__heading span');
                    if (popularDateSpan && popularDateSpan.title) {
                        const titleDateMatch = popularDateSpan.title.match(/^(\d{4}-\d{2}-\d{2})/);
                        if (titleDateMatch && titleDateMatch[1]) {
                            ctxDate = titleDateMatch[1];
                            dateFound = true;
                            if (!periodFound) { ctxPeriod = 'day'; periodFound = true; }
                        }
                    }
                }

                if (dateFound && !periodFound) {
                    ctxPeriod = 'week'; periodFound = true;
                }

                if (searchParams.toString() === '' && !dateFound) {
                    const today = new Date();
                    ctxDate = `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, '0')}-${String(today.getDate()).padStart(2, '0')}`;
                    dateFound = true;
                    if (!periodFound) { ctxPeriod = 'week'; periodFound = true; }
                    console.warn("Video Filter: Used fallback to today's date for /posts/popular.");
                }
            }

            if (dateFound && periodFound) {
                return { type: 'popular_posts', date: ctxDate, period: ctxPeriod, query: null };
            } else {
                // showStatus is called by handleUrlChangeAndSetStatus
                console.error('Video Filter: Missing date/period for /posts/popular. Final Date:', ctxDate, 'Final Period:', ctxPeriod);
                return null;
            }
        }

        if (pathname === '/posts') {
            return { type: 'global_search', query: query || null };
        }

        // showStatus is called by handleUrlChangeAndSetStatus
        console.error('Video Filter: Unknown page structure for context. Path:', pathname, 'Query:', window.location.search);
        return null;
    }

    // ... (functions: buildApiUrl, fetchData, isVideoFile, isImageFile, getPostPreviewUrl, getFirstVideoUrlFromPost, createPostCardHtml, handleFilter, handleCopyUrls - REMAINS THE SAME) ...
    function buildApiUrl(context, offset) {
        let baseApiUrl = `https://${currentDomain}/api/v1`;
        let queryParts = [`o=${offset}`];

        switch (context.type) {
            case 'profile':
                return `${baseApiUrl}/${context.service}/user/${context.userId}?${queryParts.join('&')}`;
            case 'user_search':
                if (context.query) queryParts.push(`q=${encodeURIComponent(context.query)}`);
                return `${baseApiUrl}/${context.service}/user/${context.userId}/posts-legacy?${queryParts.join('&')}`;
            case 'global_search':
                if (context.query) queryParts.push(`q=${encodeURIComponent(context.query)}`);
                return `${baseApiUrl}/posts?${queryParts.join('&')}`;
            case 'popular_posts':
                if (!context.date || !context.period) {
                    return null;
                }
                queryParts.push(`date=${encodeURIComponent(context.date)}`);
                queryParts.push(`period=${encodeURIComponent(context.period)}`);
                return `${baseApiUrl}/posts/popular?${queryParts.join('&')}`;
            default:
                return null;
        }
    }

    function fetchData(apiUrl) {
        return new Promise((resolve, reject) => {
            GM_xmlhttpRequest({
                method: "GET", url: apiUrl,
                onload: resp => {
                    if (resp.status >= 200 && resp.status < 300) {
                        try { resolve(JSON.parse(resp.responseText)); }
                        catch (e) { reject(`Error parsing JSON from ${apiUrl}: ${e.message}`); }
                    } else { reject(`API request failed for ${apiUrl}: ${resp.status} ${resp.statusText}`); }
                },
                onerror: resp => reject(`API request error for ${apiUrl}: ${resp.statusText || 'Network error'}`)
            });
        });
    }

    function isVideoFile(filenameOrPath) {
        if (!filenameOrPath) return false;
        const lowerName = filenameOrPath.toLowerCase();
        return VIDEO_EXTENSIONS.some(ext => lowerName.endsWith('.' + ext));
    }

    function isImageFile(filenameOrPath) {
        if (!filenameOrPath) return false;
        const lowerName = filenameOrPath.toLowerCase();
        return IMAGE_EXTENSIONS.some(ext => lowerName.endsWith('.' + ext));
    }

    function getPostPreviewUrl(post, apiPreviewsEntry) {
        if (apiPreviewsEntry && apiPreviewsEntry.length > 0 && apiPreviewsEntry[0]) {
            const previewData = apiPreviewsEntry[0];
            if (previewData.server && previewData.path) return `${previewData.server}${previewData.path}`;
        }
        if (post.file && post.file.path && isImageFile(post.file.path)) return `https://${currentDomain}/data${post.file.path}`;
        if (post.file && post.file.name && isImageFile(post.file.name) && post.file.name.startsWith('/')) return `https://${currentDomain}/data${post.file.name}`;
        if (post.attachments) {
            for (const attachment of post.attachments) {
                if (attachment.path && isImageFile(attachment.path)) return `https://${currentDomain}/data${attachment.path}`;
                if (attachment.name && isImageFile(attachment.name) && attachment.name.startsWith('/')) return `https://${currentDomain}/data${attachment.name}`;
            }
        }
        return null;
    }

    function getFirstVideoUrlFromPost(post) {
        const domain = `https://${currentDomain}/data`;
        if (post.file && (isVideoFile(post.file.name) || isVideoFile(post.file.path))) {
            if (post.file.path) return domain + post.file.path;
            if (post.file.name && post.file.name.startsWith('/')) return domain + post.file.name;
        }
        if (post.attachments) {
            for (const att of post.attachments) {
                if (isVideoFile(att.name) || isVideoFile(att.path)) {
                    if (att.path) return domain + att.path;
                    if (att.name && att.name.startsWith('/')) return domain + att.name;
                }
            }
        }
        return null;
    }

    function createPostCardHtml(post, previewUrl) {
        const postDate = new Date(post.published || post.added);
        const formattedDate = postDate.toLocaleString();
        const dateTimeAttr = postDate.toISOString();
        const attachmentCount = post.attachments ? post.attachments.length : 0;
        const attachmentText = attachmentCount === 1 ? "1 Attachment" : `${attachmentCount} Attachments`;

        let displayTitle = (post.title && post.title.trim()) ? post.title.trim() : '';
        let potentialContent = post.content || post.substring || '';
        if (!displayTitle && potentialContent) {
            const tempDiv = document.createElement('div');
            tempDiv.innerHTML = potentialContent;
            let contentForTitle = (tempDiv.textContent || tempDiv.innerText || "").trim();
            if (contentForTitle) {
                displayTitle = contentForTitle.substring(0, SUBSTRING_TITLE_LENGTH) + (contentForTitle.length > SUBSTRING_TITLE_LENGTH ? '...' : '');
            }
        }
        displayTitle = displayTitle || 'No Title';

        let mediaHtml = '';
        const firstVideoUrl = getFirstVideoUrlFromPost(post);

        if (firstVideoUrl) {
            const posterAttribute = previewUrl ? `poster="${previewUrl}"` : '';
            mediaHtml = `
        <div class="post-card__image-container" style="text-align: center; margin-bottom: 5px; background-color: #000;">
            <video class="lazy-load-video" controls preload="none" width="100%" style="max-height: 300px; display: block;" ${posterAttribute}>
                <source data-src="${firstVideoUrl}" type="video/mp4">
                Your browser does not support the video tag.
            </video>
        </div>`;
        } else if (previewUrl) {
            mediaHtml = `
        <div class="post-card__image-container" style="text-align: center; margin-bottom: 5px;">
            <img class="post-card__image" src="${previewUrl}" alt="Preview for post ${post.id}" style="max-width: 100%; height: auto; max-height: 200px; object-fit: contain; border: 1px solid #444;">
        </div>`;
        } else {
            mediaHtml = `
        <div class="post-card__image-container" style="text-align: center; margin-bottom: 5px; height: 100px; display: flex; align-items: center; justify-content: center; background-color: #333; color: #aaa; font-size:0.9em; border: 1px solid #444;">
            No Preview Available
        </div>`;
        }

        const postLink = `/${post.service}/user/${post.user}/post/${post.id}`;
        return `
    <article class="post-card post-card--preview" data-id="${post.id}" data-service="${post.service}" data-user="${post.user}">
      <a class="fancy-link fancy-link--kemono" href="${postLink}" target="_blank" rel="noopener noreferrer">
       <header class="post-card__header" title="${displayTitle.replace(/"/g, '"')}">${displayTitle}</header>
       ${mediaHtml}
       <footer class="post-card__footer">
         <div>
            <div>
              <time class="timestamp" datetime="${dateTimeAttr}">${formattedDate}</time>
              <div>${attachmentCount > 0 ? attachmentText : 'No Attachments'}</div>
            </div>
          </div>
        </footer>
      </a>
    </article>`;
    }

    async function handleFilter() {
        showStatus(''); // Clear previous status from handleUrlChangeAndSetStatus if any
        filterButton.textContent = 'Filter Videos';
        styleButton(filterButton, true); filterButton.disabled = true;
        styleButton(copyUrlsButton, true); copyUrlsButton.disabled = true;
        allFoundVideoUrls = [];

        setupVideoIntersectionObserver();

        const pageRangeStr = pageRangeInput.value;
        const pagesToFetch = parsePageRange(pageRangeStr);
        if (!pagesToFetch) {
            styleButton(filterButton, false); filterButton.disabled = false; // Re-enable if parsing failed
            return;
        }

        const context = determinePageContext(); // Re-check context in case of race condition (unlikely here)
        if (!context) {
             showStatus('Error: Page context invalid for filtering. Try navigating again.', 'error');
            styleButton(filterButton, false); filterButton.disabled = false; // Re-enable as it was user action
            return;
        }

        const postListContainer = document.querySelector('.card-list__items');
        if (!postListContainer) {
            showStatus('Error: Post container (.card-list__items) not found.', 'error');
            styleButton(filterButton, false); filterButton.disabled = false;
            return;
        }
        postListContainer.style.setProperty('--card-size', '350px');
        postListContainer.innerHTML = ''; // Clear previous results from this container

        document.querySelectorAll('.paginator menu, .content > menu.Paginator').forEach(menu => menu.style.display = 'none');
        const paginatorInfo = document.querySelector('.paginator > small, .content > div > small.subtle-text');
        if (paginatorInfo) paginatorInfo.textContent = `Filtering posts...`;

        let totalVideoPostsFound = 0;

        for (let i = 0; i < pagesToFetch.length; i++) {
            const pageNum = pagesToFetch[i];
            const offset = (pageNum - 1) * POSTS_PER_PAGE;
            const apiUrl = buildApiUrl(context, offset);
            if (!apiUrl) {
                showStatus('Error: Could not build API URL. Context may be invalid.', 'error');
                break;
            }

            filterButton.textContent = `Filtering Page ${i + 1} / ${pagesToFetch.length}...`;
            showStatus(`Fetching page ${pageNum} (offset ${offset})...`, 'info');

            try {
                const apiResponse = await fetchData(apiUrl);
                let posts = [], resultPreviews = null;

                if (Array.isArray(apiResponse)) {
                    posts = apiResponse;
                } else if (apiResponse.results && Array.isArray(apiResponse.results)) {
                    posts = apiResponse.results;
                    resultPreviews = apiResponse.result_previews;
                } else if (apiResponse.posts && Array.isArray(apiResponse.posts)) {
                    posts = apiResponse.posts;
                    resultPreviews = apiResponse.result_previews;
                } else {
                    showStatus(`Warning: Unexpected API response structure for page ${pageNum}.`, 'error');
                    console.warn("Unexpected API response:", apiResponse);
                    continue;
                }

                for (let postIndex = 0; postIndex < posts.length; postIndex++) {
                    const post = posts[postIndex];
                    let isVideo = false, postVideoUrls = [];

                    if (post.file && (isVideoFile(post.file.name) || isVideoFile(post.file.path))) {
                        isVideo = true;
                        if (post.file.path) postVideoUrls.push(`https://${currentDomain}/data${post.file.path}`);
                        else if (post.file.name && post.file.name.startsWith('/')) postVideoUrls.push(`https://${currentDomain}/data${post.file.name}`);
                    }
                    if (post.attachments) {
                        post.attachments.forEach(att => {
                            if (isVideoFile(att.name) || isVideoFile(att.path)) {
                                isVideo = true;
                                if (att.path) postVideoUrls.push(`https://${currentDomain}/data${att.path}`);
                                else if (att.name && att.name.startsWith('/')) postVideoUrls.push(`https://${currentDomain}/data${att.name}`);
                            }
                        });
                    }

                    if (isVideo) {
                        totalVideoPostsFound++;
                        allFoundVideoUrls.push(...postVideoUrls);
                        const apiPreviewEntry = resultPreviews ? resultPreviews[postIndex] : null;
                        const previewUrl = getPostPreviewUrl(post, apiPreviewEntry);
                        const cardHtml = createPostCardHtml(post, previewUrl);
                        postListContainer.insertAdjacentHTML('beforeend', cardHtml);
                    }
                }
                if (paginatorInfo) paginatorInfo.textContent = `Showing ${totalVideoPostsFound} video posts from selected pages.`;

            } catch (error) {
                showStatus(`Error fetching page ${pageNum}: ${error}`, 'error');
                console.error("Filter error:", error);
            }
            if (i < pagesToFetch.length - 1) await new Promise(resolve => setTimeout(resolve, API_DELAY));
        }

        const videoElementsInContainer = postListContainer.querySelectorAll('video.lazy-load-video');
        videoElementsInContainer.forEach(videoEl => {
            if (videoEl.querySelector('source[data-src]') && videoIntersectionObserver) {
                videoIntersectionObserver.observe(videoEl);
            }
        });

        filterButton.textContent = 'Filter Videos';
        styleButton(filterButton, false); filterButton.disabled = false;

        if (totalVideoPostsFound > 0) {
            showStatus(`Filter complete. Found ${totalVideoPostsFound} video posts.`, 'success');
            styleButton(copyUrlsButton, false); copyUrlsButton.disabled = false;
        } else {
            showStatus('Filter complete. No video posts found on selected pages.', 'info');
            styleButton(copyUrlsButton, true); copyUrlsButton.disabled = true;
        }
    }
    filterButton.addEventListener('click', handleFilter);


    function handleCopyUrls() {
        if (allFoundVideoUrls.length === 0) {
            showStatus("No video URLs to copy.", 'error');
            return;
        }
        const uniqueUrls = [...new Set(allFoundVideoUrls)];
        GM_setClipboard(uniqueUrls.join('\n'));
        const originalText = copyUrlsButton.textContent;
        copyUrlsButton.textContent = `Copied ${uniqueUrls.length} URLs!`;
        showStatus(`Copied ${uniqueUrls.length} unique video URLs!`, 'success');
        setTimeout(() => { copyUrlsButton.textContent = originalText; }, 3000);
    }
    copyUrlsButton.addEventListener('click', handleCopyUrls);


    // --- NEW: Function to handle URL changes and set initial status ---
    function handleUrlChangeAndSetStatus() {
        console.log("Video Filter: URL change detected or initial load. Re-evaluating context.");
        const currentContext = determinePageContext();

        // Reset states that depend on previous page's filter results
        allFoundVideoUrls = [];
        styleButton(copyUrlsButton, true); // Disable
        copyUrlsButton.disabled = true;
        copyUrlsButton.textContent = 'Copy Video URLs'; // Reset text

        // It's also good to disconnect observer if page changes entirely
        if (videoIntersectionObserver) {
            videoIntersectionObserver.disconnect();
        }
        // Clear the visual results if the page context changes significantly
        // const postListContainer = document.querySelector('.card-list__items');
        // if (postListContainer) postListContainer.innerHTML = '';


        if (currentContext) {
            showStatus("Video filter ready. Enter page range and click 'Filter Videos'.", 'info');
            styleButton(filterButton, false); // Enable
            filterButton.disabled = false;
        } else {
            showStatus("Page context could not be determined. Filter may not be available. Check console.", 'error');
            styleButton(filterButton, true); // Disable
            filterButton.disabled = true;
        }
    }

    // Wrap history API methods to detect SPA navigation
    const originalPushState = history.pushState;
    history.pushState = function() {
        const result = originalPushState.apply(this, arguments);
        // Dispatch a custom event that our script can listen to
        window.dispatchEvent(new Event('custompushstate'));
        return result;
    };

    const originalReplaceState = history.replaceState;
    history.replaceState = function() {
        const result = originalReplaceState.apply(this, arguments);
        window.dispatchEvent(new Event('customreplacestate'));
        return result;
    };

    // Listen to navigation events
    window.addEventListener('popstate', handleUrlChangeAndSetStatus);
    window.addEventListener('custompushstate', handleUrlChangeAndSetStatus);
    window.addEventListener('customreplacestate', handleUrlChangeAndSetStatus);

    // Initial call to set status and button states based on the current URL
    handleUrlChangeAndSetStatus();

})();