// ==UserScript==
// @name         F95Zone "compressed version" Link Above Thumbnails
// @namespace    http://tampermonkey.net/
// @version      1.14
// @description  Insert "compressed version" link above thumbnails pointing to oldest compressed post via search analysis
// @match        https://f95zone.to/threads/*
// @grant        GM_xmlhttpRequest
// @connect      f95zone.to
// ==/UserScript==

(function() {
    'use strict';

    function extractVersion(titleText) {
        const versionRegex = /v(?:ersion)?[\s\-_]?(\d+(?:\.\d+){0,2})/i;
        const cleaned = titleText.replace(/[\[\]\(\)]/g, ' ');
        const match = cleaned.match(versionRegex);
        return (match && match[1]) ? 'v' + match[1] : null;
    }

    function createCompressedVersionLink(label, href) {
        const link = document.createElement('a');
        link.href = href;
        link.textContent = label;
        link.target = '_blank';
        link.style.cssText = `
            display: inline-block;
            margin: 10px 0 12px 0;
            padding: 8px 14px;
            background-color: #ba4545;
            color: white;
            font-weight: bold;
            font-size: 14px;
            text-decoration: none;
            border-radius: 4px;
            cursor: pointer;
        `;
        link.title = 'Go to compressed version post';
        return link;
    }

    function fetchPage(url) {
        if (typeof GM_xmlhttpRequest === "function") {
            return new Promise((resolve, reject) => {
                GM_xmlhttpRequest({
                    method: "GET",
                    url: url,
                    headers: {"User-Agent": navigator.userAgent},
                    onload: r => r.status >= 200 && r.status < 300 ? resolve(r.responseText) : reject(new Error(`HTTP ${r.status}`)),
                    onerror: reject
                });
            });
        } else {
            return fetch(url).then(r => {
                if (!r.ok) throw new Error(r.statusText);
                return r.text();
            });
        }
    }

    function findOldestCompressedPost(html) {
        const doc = new DOMParser().parseFromString(html, 'text/html');
        const items = [...doc.querySelectorAll('li.block-row.js-inlineModContainer')];

        const candidates = items.map(li => {
            const contentRow = li.querySelector('.contentRow-main');
            if (!contentRow) return null;
            const postLinkElem = contentRow.querySelector('h3.contentRow-title a[href]');
            if (!postLinkElem) return null;
            const postUrl = postLinkElem.href;

            const snippetElem = contentRow.querySelector('.contentRow-snippet');
            if (!snippetElem) return null;
            const snippetText = snippetElem.textContent || '';

            if (!/compressed/i.test(snippetText)) return null;

            const timeElem = li.querySelector('time.u-dt');
            if (!timeElem) return null;
            const datetime = timeElem.getAttribute('datetime');
            if (!datetime) return null;
            const dateObj = new Date(datetime);
            if (isNaN(dateObj.getTime())) return null;

            return { url: postUrl, date: dateObj };
        }).filter(Boolean);

        if (candidates.length === 0) return null;

        candidates.sort((a, b) => a.date - b.date);
        return candidates[0].url;
    }

    function insertLinkAboveDownloadDiv(firstPost, linkOrElem) {
        // Find the big "DOWNLOAD" div by inline style text-align: center and text content with "DOWNLOAD"
        const candidates = firstPost.querySelectorAll('div[style*="text-align: center"]');
        for (const div of candidates) {
            if (div.textContent.trim().toUpperCase().includes('DOWNLOAD')) {
                div.parentNode.insertBefore(linkOrElem, div);
                return true;
            }
        }
        return false;
    }

    window.addEventListener('load', () => {
        try {
            const titleElem = document.querySelector('h1.p-title-value');
            if (!titleElem) return;

            const version = extractVersion(titleElem.textContent || '');
            const searchQuery = version ? `compressed ${version}` : 'compressed';

            const firstPost = document.querySelector('.message-threadStarterPost.message--post.js-post');
            if (!firstPost) return;

            if (/compressed/i.test(firstPost.textContent)) {
                // Already has compressed word in first post, no insertion
                return;
            }

            const threadIdMatch = window.location.pathname.match(/\.([0-9]+)(?:\/|$)/);
            if (!threadIdMatch) return;
            const threadId = threadIdMatch[1];

            const searchUrl = `${window.location.origin}/search/?q=${encodeURIComponent(searchQuery)}&t=post&c[thread]=${threadId}&o=date`;

            fetchPage(searchUrl).then(html => {
                const oldestUrl = findOldestCompressedPost(html);

                if (!oldestUrl) {
                    const noLinkSpan = document.createElement('span');
                    noLinkSpan.textContent = 'No compressed version link found';
                    noLinkSpan.style.color = '#ba4545';
                    noLinkSpan.style.display = 'block';
                    noLinkSpan.style.margin = '10px 0 12px 0';
                    insertLinkAboveDownloadDiv(firstPost, noLinkSpan);
                    return;
                }

                const link = createCompressedVersionLink('compressed version', oldestUrl);
                insertLinkAboveDownloadDiv(firstPost, link);
            }).catch(console.error);
        } catch (e) {
            console.error(e);
        }
    });

})();
--------------------------------------------
script above is not working properly it shows no compressed version link found, older script below working but it doesn't have function that I want in newer script. btw search results already ordered by date where newest at top and oldest at down:
// ==UserScript==
// @name         F95Zone "Compressed" + version search button
// @namespace    http://tampermonkey.net/
// @version      1.10
// @description  Adds a button that searches for "compressed" + version number on F95Zone threads
// @author       Vic (modified)
// @match        https://f95zone.to/threads/*
// @icon         https://www.google.com/s2/favicons?domain=f95zone.to
// @grant        none
// @license      GPL-2.0
// @downloadURL  https://update.greasyfork.org/scripts/500598/F95Zone%20%22Compressed%22%20search%20button.user.js
// @updateURL    https://update.greasyfork.org/scripts/500598/F95Zone%20%22Compressed%22%20search%20button.meta.js
// ==/UserScript==

(function() {
    'use strict';

    // Utility to extract version number from thread title text
    function extractVersion(titleText) {
        // Match v followed by digits and optional decimals, possibly inside brackets [] or ()
        // e.g. v0.4, v0.35, [v0.4], (v0.4), v1.0.2 etc.
        // It extracts only the version string, without brackets
        const versionRegex = /v(?:ersion)?[\s\-_]?(\d+(?:\.\d+){0,2})/i;
        // Sometimes version is inside square or round brackets, so strip first
        // Or inside brackets like [v0.4], so consider that: first remove brackets then search
        const cleaned = titleText.replace(/[\[\]\(\)]/g, ' ');
        const match = cleaned.match(versionRegex);
        if (match && match[1]) {
            return 'v' + match[1];
        }
        return null;
    }

    // Create a new button with a custom WinRAR-style icon
    const button = document.createElement('button');
    button.innerHTML = `
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 17.333 17.333" fill="#fff">
            <path d="M14.75,0H2.585C2.329,0,2.12,0.208,2.12,0.467v8.881v1.653v5.866c0,0.257,0.209,0.466,0.466,0.466
                h9.072c0.13,0,0.257-0.057,0.345-0.156l3.091-3.461c0.077-0.086,0.118-0.194,0.118-0.31V0.467C15.214,0.208,15.006,0,14.75,0z
                 M13.746,12.114l-2.774-0.002c-0.137,0-0.269,0.056-0.366,0.152s-0.151,0.228-0.151,0.366l0.01,3.47H3.517v-5.099V9.348V1.167h4.59
                v0.521h1.117V1.167h4.522V12.114z M9.224,6.513H8.107v-1.93h1.117V6.513z M9.224,4.1H8.107V2.169h1.117V4.1z M9.225,8.114V6.997
                H8.108v1.117h0.071v0.031C7.906,8.169,7.69,8.395,7.69,8.674v4.473c0,0.294,0.239,0.536,0.533,0.536h0.889
                c0.293,0,0.533-0.24,0.533-0.536V8.674c0-0.279-0.217-0.506-0.487-0.529V8.114H9.225z M9.289,11.475v1.29
                c0,0.295-0.238,0.534-0.533,0.534H8.555c-0.294,0-0.532-0.238-0.532-0.534v-1.29c0-0.293,0.238-0.533,0.532-0.533h0.201
                C9.05,10.942,9.289,11.181,9.289,11.475z"/>
        </svg>
    `;
    button.style.position = 'fixed';
    button.style.top = '10px';
    button.style.right = '10px';
    button.style.zIndex = '1000';
    button.style.padding = '10px';
    button.style.backgroundColor = '#ba4545';
    button.style.color = '#fff';
    button.style.border = 'none';
    button.style.borderRadius = '50%'; // to make it round
    button.style.width = '40px';
    button.style.height = '40px';
    button.style.cursor = 'pointer';

    // Append the button to the body
    document.body.appendChild(button);

    // Add click event to the button
    button.addEventListener('click', function() {
        // Attempt to find the thread title element
        const titleElem = document.querySelector('h1.p-title-value');
        if (!titleElem) {
            alert('Thread title not found! Cannot extract version');
            return;
        }

        const titleText = titleElem.textContent || '';
        const version = extractVersion(titleText);

        // Compose the search phrase
        let searchPhrase = 'compressed';
        if (version) {
            searchPhrase += ' ' + version;
        }

        // Find the search box and search button
        const searchBox = document.querySelector('input[name="keywords"]');
        const searchButton = document.querySelector('button[type="submit"]');

        if (searchBox && searchButton) {
            // Set the value of the search box to the phrase and trigger input events to ensure the value is recognized
            searchBox.value = searchPhrase;
            searchBox.dispatchEvent(new Event('input', { bubbles: true }));
            searchBox.dispatchEvent(new Event('change', { bubbles: true }));

            // Add a small delay before clicking the search button
            setTimeout(() => {
                searchButton.click();
            }, 100); // Delay of 100 milliseconds
        } else {
            alert('Search box or search button not found!');
        }
    });
})();