Greasy Fork is available in English.

Advanced YouTube Search

Enhance YouTube search with both regular and advanced parameters like exact terms, exclusions, title includes, video length, and date range.

// ==UserScript==
// @name         Advanced YouTube Search
// @namespace    http://tampermonkey.net/
// @version      1.1
// @description  Enhance YouTube search with both regular and advanced parameters like exact terms, exclusions, title includes, video length, and date range.
// @author       bitgineer https://github.com/bitgineer
// @match        https://www.youtube.com/*
// @grant        none
// @run-at       document-end
// @license MIT
// ==/UserScript==

(function() {
    'use strict';

    // Wait until the YouTube page is fully loaded
    function waitForElement(selector, callback) {
        const observer = new MutationObserver((mutations, me) => {
            const element = document.querySelector(selector);
            if (element) {
                me.disconnect();
                callback(element);
            }
        });
        observer.observe(document.body, { childList: true, subtree: true });
    }

    // Mapping for video length to 'sp' parameter
    const videoLengthOptions = {
        "any": "",
        "short": "EgIYAQ%253D%253D",      // Under 4 minutes
        "medium": "EgIYAw%253D%253D",     // 4-20 minutes
        "long": "EgIYAg%253D%253D"        // Over 20 minutes
    };

    // Create and inject the toggle button and advanced search popup
    function injectAdvancedSearchPopup() {
        // Create toggle button
        const toggleButton = document.createElement('button');
        toggleButton.innerText = '🔍 Advanced Search';
        toggleButton.style.position = 'fixed';
        toggleButton.style.bottom = '20px';
        toggleButton.style.right = '20px';
        toggleButton.style.zIndex = '1000';
        toggleButton.style.padding = '10px 15px';
        toggleButton.style.backgroundColor = '#FF0000';
        toggleButton.style.color = '#fff';
        toggleButton.style.border = 'none';
        toggleButton.style.borderRadius = '4px';
        toggleButton.style.cursor = 'pointer';
        toggleButton.style.boxShadow = '0 2px 6px rgba(0,0,0,0.3)';
        toggleButton.style.fontSize = '14px';
        toggleButton.style.fontWeight = 'bold';
        toggleButton.title = 'Toggle Advanced YouTube Search';

        document.body.appendChild(toggleButton);

        // Create the popup container
        const popupDiv = document.createElement('div');
        popupDiv.id = 'advancedSearchPopup';
        popupDiv.style.position = 'fixed';
        popupDiv.style.top = '100px';
        popupDiv.style.left = '50%';
        popupDiv.style.transform = 'translateX(-50%)';
        popupDiv.style.width = '450px';
        popupDiv.style.backgroundColor = '#f9f9f9';
        popupDiv.style.border = '1px solid #ccc';
        popupDiv.style.borderRadius = '8px';
        popupDiv.style.boxShadow = '0 4px 12px rgba(0,0,0,0.3)';
        popupDiv.style.padding = '0';
        popupDiv.style.zIndex = '1001';
        popupDiv.style.display = 'none'; // Initially hidden
        popupDiv.style.cursor = 'default';

        // Create the popup header for dragging
        const popupHeader = document.createElement('div');
        popupHeader.style.backgroundColor = '#FF0000';
        popupHeader.style.color = '#fff';
        popupHeader.style.padding = '10px';
        popupHeader.style.cursor = 'move';
        popupHeader.style.borderTopLeftRadius = '8px';
        popupHeader.style.borderTopRightRadius = '8px';
        popupHeader.style.fontSize = '16px';
        popupHeader.style.fontWeight = 'bold';
        popupHeader.innerText = 'Advanced YouTube Search';

        popupDiv.appendChild(popupHeader);

        // Create the form container
        const formContainer = document.createElement('div');
        formContainer.style.padding = '15px';
        popupDiv.appendChild(formContainer);

        // Create form element
        const form = document.createElement('form');

        // Helper function to create form fields
        function createField(labelText, inputType, inputName, placeholder='', options=[]) {
            const div = document.createElement('div');
            div.style.marginBottom = '12px';

            const label = document.createElement('label');
            label.innerText = labelText;
            label.htmlFor = inputName;
            label.style.display = 'block';
            label.style.marginBottom = '6px';
            label.style.fontWeight = 'bold';

            let input;
            if (inputType === 'select') {
                input = document.createElement('select');
                input.name = inputName;
                input.id = inputName;

                // Add options based on select options provided
                options.forEach(option => {
                    const opt = document.createElement('option');
                    opt.value = option.value;
                    opt.text = option.text;
                    input.appendChild(opt);
                });
            } else if (inputType === 'date') {
                input = document.createElement('input');
                input.type = 'date';
                input.name = inputName;
                input.id = inputName;
            } else {
                input = document.createElement('input');
                input.type = inputType;
                input.name = inputName;
                input.id = inputName;
                input.placeholder = placeholder;
            }

            input.style.width = '100%';
            input.style.padding = '8px';
            input.style.boxSizing = 'border-box';
            input.style.border = '1px solid #ccc';
            input.style.borderRadius = '4px';

            div.appendChild(label);
            div.appendChild(input);
            return div;
        }

        // === Regular Search Section ===
        const regularSearchSection = document.createElement('div');
        regularSearchSection.style.marginBottom = '20px';

        const regularSearchTitle = document.createElement('h3');
        regularSearchTitle.innerText = 'Regular Search';
        regularSearchTitle.style.marginTop = '0';
        regularSearchTitle.style.marginBottom = '10px';
        regularSearchTitle.style.fontSize = '18px';
        regularSearchTitle.style.color = '#333';

        regularSearchSection.appendChild(regularSearchTitle);

        const regularSearchInput = createField('Search:', 'text', 'regularSearch', 'e.g., funny cats');

        regularSearchSection.appendChild(regularSearchInput);

        form.appendChild(regularSearchSection);

        // === Advanced Search Section ===
        const advancedSearchSection = document.createElement('div');

        const advancedSearchTitle = document.createElement('h3');
        advancedSearchTitle.innerText = 'Advanced Options';
        advancedSearchTitle.style.marginTop = '0';
        advancedSearchTitle.style.marginBottom = '10px';
        advancedSearchTitle.style.fontSize = '18px';
        advancedSearchTitle.style.color = '#333';

        advancedSearchSection.appendChild(advancedSearchTitle);

        // Exact Term
        advancedSearchSection.appendChild(createField('Exact Term:', 'text', 'exactTerm', 'e.g., "Awesome List"'));

        // Exclude Term
        advancedSearchSection.appendChild(createField('Exclude Term(s):', 'text', 'excludeTerms', 'e.g., spam, ads'));

        // Title Includes
        advancedSearchSection.appendChild(createField('Title Includes:', 'text', 'titleIncludes', 'e.g., tutorial'));

        // Video Length
        advancedSearchSection.appendChild(createField('Video Length:', 'select', 'videoLength', '', [
            {value: 'any', text: 'Any Length'},
            {value: 'short', text: 'Under 4 minutes'},
            {value: 'medium', text: '4-20 minutes'},
            {value: 'long', text: 'Over 20 minutes'}
        ]));

        // Date After
        advancedSearchSection.appendChild(createField('Date After:', 'date', 'dateAfter'));

        // Date Before
        advancedSearchSection.appendChild(createField('Date Before:', 'date', 'dateBefore'));

        form.appendChild(advancedSearchSection);

        // Submit Button
        const submitBtn = document.createElement('button');
        submitBtn.type = 'submit';
        submitBtn.innerText = 'Search';
        submitBtn.style.padding = '10px 20px';
        submitBtn.style.backgroundColor = '#FF0000';
        submitBtn.style.color = '#fff';
        submitBtn.style.border = 'none';
        submitBtn.style.borderRadius = '4px';
        submitBtn.style.cursor = 'pointer';
        submitBtn.style.fontSize = '16px';
        submitBtn.style.fontWeight = 'bold';
        submitBtn.style.width = '100%';
        submitBtn.style.marginTop = '10px';

        form.appendChild(submitBtn);

        // Handle form submission
        form.addEventListener('submit', function(e) {
            e.preventDefault();

            // Get form values
            const regularSearch = document.getElementById('regularSearch').value.trim();
            const exactTerm = document.getElementById('exactTerm').value.trim();
            const excludeTerms = document.getElementById('excludeTerms').value.trim();
            const titleIncludes = document.getElementById('titleIncludes').value.trim();
            const videoLength = document.getElementById('videoLength').value;
            const dateAfter = document.getElementById('dateAfter').value;
            const dateBefore = document.getElementById('dateBefore').value;

            let query = '';

            // Add regular search term
            if (regularSearch) {
                query += `${regularSearch}`;
            }

            // Add exact term
            if (exactTerm) {
                if (query.length > 0) query += ' ';
                query += `"${exactTerm}"`;
            }

            // Add exclude terms
            if (excludeTerms) {
                const excludes = excludeTerms.split(',').map(term => term.trim()).filter(term => term);
                excludes.forEach(term => {
                    query += ` -${term}`;
                });
            }

            // Add title includes
            if (titleIncludes) {
                const titles = titleIncludes.split(',').map(term => term.trim()).filter(term => term);
                titles.forEach(term => {
                    query += ` intitle:${term}`;
                });
            }

            // Add date filters
            if (dateAfter) {
                query += ` after:${dateAfter}`;
            }
            if (dateBefore) {
                query += ` before:${dateBefore}`;
            }

            // Encode the query
            const encodedQuery = encodeURIComponent(query.trim());

            // Construct the search URL
            let searchURL = `https://www.youtube.com/results?search_query=${encodedQuery}`;

            // Add video length filter if not 'any'
            if (videoLength !== 'any') {
                searchURL += `&sp=${videoLengthOptions[videoLength]}`;
            }

            // Redirect to the constructed search URL
            window.location.href = searchURL;
        });

        formContainer.appendChild(form);
        popupDiv.appendChild(formContainer);
        document.body.appendChild(popupDiv);

        // Toggle button functionality
        toggleButton.addEventListener('click', (e) => {
            e.stopPropagation(); // Prevent event from bubbling up
            if (popupDiv.style.display === 'none') {
                popupDiv.style.display = 'block';
            } else {
                popupDiv.style.display = 'none';
            }
        });

        // Prevent popup click from closing when clicking inside
        popupDiv.addEventListener('click', (e) => {
            e.stopPropagation();
        });

        // Implement dragging functionality
        let isDragging = false;
        let offsetX, offsetY;

        popupHeader.addEventListener('mousedown', (e) => {
            isDragging = true;
            const rect = popupDiv.getBoundingClientRect();
            offsetX = e.clientX - rect.left;
            offsetY = e.clientY - rect.top;
            document.addEventListener('mousemove', onMouseMove);
            document.addEventListener('mouseup', onMouseUp);
        });

        function onMouseMove(e) {
            if (isDragging) {
                let newLeft = e.clientX - offsetX;
                let newTop = e.clientY - offsetY;

                // Ensure the popup stays within the viewport
                const popupWidth = popupDiv.offsetWidth;
                const popupHeight = popupDiv.offsetHeight;
                const windowWidth = window.innerWidth;
                const windowHeight = window.innerHeight;

                if (newLeft < 0) newLeft = 0;
                if (newTop < 0) newTop = 0;
                if (newLeft + popupWidth > windowWidth) newLeft = windowWidth - popupWidth;
                if (newTop + popupHeight > windowHeight) newTop = windowHeight - popupHeight;

                popupDiv.style.left = `${newLeft}px`;
                popupDiv.style.top = `${newTop}px`;
                popupDiv.style.transform = 'none'; // Disable transform when dragging
            }
        }

        function onMouseUp() {
            isDragging = false;
            document.removeEventListener('mousemove', onMouseMove);
            document.removeEventListener('mouseup', onMouseUp);
        }

        // Optional: Close popup when clicking outside of it
        document.addEventListener('click', function(event) {
            if (!popupDiv.contains(event.target) && event.target !== toggleButton) {
                popupDiv.style.display = 'none';
            }
        });

    }

    // Initialize the script by injecting the popup
    waitForElement('body', function() {
        injectAdvancedSearchPopup();
    });

})();