Загрузка данных


(async function() {
    // ================= SETTINGS =================
    const START_PAGE = 20481; // Starting page
    const END_PAGE = 22400;   // Ending page (last)
    const TARGET_COLOR = "#FFA000"; // Color HEX code
    
    // Arrays for sorting accounts
    const group3to12 = [];
    const group12to36 = [];
    const group36plus = [];

    // ================= HELPER FUNCTIONS =================
    const delay = ms => new Promise(res => setTimeout(res, ms));
    const mins = m => m * 60 * 1000;

    const currentDate = new Date();

    function parseDateString(dateStr) {
        const match = dateStr.match(/(\d{2})[-./](\d{2})[-./](\d{4})/);
        if (match) {
            const day = parseInt(match[1], 10);
            const month = parseInt(match[2], 10) - 1; 
            const year = parseInt(match[3], 10);
            return new Date(year, month, day);
        }
        return null;
    }

    function getMonthsDiff(pastDate) {
        const diffYears = currentDate.getFullYear() - pastDate.getFullYear();
        const diffMonths = currentDate.getMonth() - pastDate.getMonth();
        let totalMonths = (diffYears * 12) + diffMonths;

        if (currentDate.getDate() < pastDate.getDate()) {
            totalMonths--;
        }
        return totalMonths;
    }

    // Smart page parsing
    function extractDataFromHTML(html) {
        const parser = new DOMParser();
        const doc = parser.parseFromString(html, 'text/html');
        const spans = doc.querySelectorAll('span');
        
        const targetSpans = Array.from(spans).filter(span => {
            const styleAttr = span.getAttribute('style');
            if (!styleAttr) return false;
            const formattedStyle = styleAttr.replace(/\s/g, '').toUpperCase();
            return formattedStyle.includes(TARGET_COLOR.toUpperCase()) || formattedStyle.includes('RGB(255,160,0)');
        });

        targetSpans.forEach(span => {
            const nickname = span.textContent.trim();

            if (/^\d+$/.test(nickname)) {
                return;
            }

            let userCard = null;
            const dateRegex = /\d{2}[-./]\d{2}[-./]\d{4}/g;

            // Classic table layout
            if (span.closest('tr')) {
                userCard = span.closest('tr');
            } else {
                // Search for individual user container
                let current = span.parentElement;
                while (current && current.tagName !== 'BODY') {
                    const text = current.textContent;
                    const datesCount = (text.match(dateRegex) || []).length;
                    
                    if (datesCount >= 1 && datesCount <= 2) {
                        const parentText = current.parentElement ? current.parentElement.textContent : "";
                        const parentDatesCount = (parentText.match(dateRegex) || []).length;
                        
                        // If the parent has more than 2 dates, we have moved out to the general list.
                        // Capture the card.
                        if (parentDatesCount > 2) {
                            userCard = current;
                            break;
                        }
                    }
                    current = current.parentElement;
                }
                
                // Fallback method
                if (!userCard) {
                    userCard = span.closest('.row') || span.parentElement;
                }
            }

            if (userCard) {
                // Collect text only from the found card
                const allTexts = Array.from(userCard.querySelectorAll('*')).map(el => el.textContent.trim());
                let targetDateStr = null;

                // Grab the last date (Last visit)
                for (let i = allTexts.length - 1; i >= 0; i--) {
                    const match = allTexts[i].match(/\d{2}[-./]\d{2}[-./]\d{4}/);
                    if (match) {
                        targetDateStr = match[0];
                        break;
                    }
                }

                if (targetDateStr) {
                    const parsedDate = parseDateString(targetDateStr);
                    if (parsedDate) {
                        const monthsDiff = getMonthsDiff(parsedDate);

                        if (monthsDiff >= 3 && monthsDiff < 12) {
                            group3to12.push(nickname);
                        } else if (monthsDiff >= 12 && monthsDiff < 36) {
                            group12to36.push(nickname);
                        } else if (monthsDiff >= 36) {
                            group36plus.push(nickname);
                        }
                    }
                }
            }
        });
    }

    // ================= FILE OPERATIONS =================
    async function updateRealTimeFile(fileHandle, dataArray) {
        if (!fileHandle || dataArray.length === 0) return;
        
        try {
            // Remove duplicates
            const uniqueContent = [...new Set(dataArray)].join('\n');
            const writable = await fileHandle.createWritable();
            await writable.write(uniqueContent);
            await writable.close();
        } catch (err) {
            console.error("%c[WRITE ERROR]%c Failed to update file:", 'color: #dc3545', 'color: inherit', err);
        }
    }

    // ================= LAUNCH AND INTERFACE =================
    async function startProcess() {
        let dirHandle;
        let handle3to12, handle12to36, handle36plus;

        try {
            // Request save folder from the user
            dirHandle = await window.showDirectoryPicker({ mode: 'readwrite' });
            
            // Create files in the selected folder
            handle3to12 = await dirHandle.getFileHandle('accounts_3_to_12_months.txt', { create: true });
            handle12to36 = await dirHandle.getFileHandle('accounts_12_to_36_months.txt', { create: true });
            handle36plus = await dirHandle.getFileHandle('accounts_36plus_months.txt', { create: true });
            
            console.log(`%c[FILES CREATED]%c Files successfully linked to the selected folder.`, 'color: #28a745', 'color: inherit');
        } catch (error) {
            console.error("Parsing cancelled. Folder access is required for the script to work.", error);
            return; // Stop execution if user denied access
        }

        let errorStage = 0; 
        let page = START_PAGE;
        
        // Variables to track array changes (to avoid unnecessary rewrites)
        let lastSizes = { g1: 0, g2: 0, g3: 0 };

        console.log(`%c[START]%c Starting parsing from page ${START_PAGE} to ${END_PAGE}...`, 'font-weight: bold; color: #6f42c1', 'color: inherit');

        while (page <= END_PAGE) {
            const url = `https://aimware.net/forum/memberlist/page/${page}/sort/regdate/order/ascending`;

            try {
                console.log(`%c[SCANNING]%c Page ${page}...`, 'color: #007bff', 'color: inherit');
                const response = await fetch(url);

                if (!response.ok) {
                    throw new Error(`HTTP Error ${response.status}`);
                }

                const html = await response.text();
                extractDataFromHTML(html);
                console.log(`%c[SUCCESS]%c Page ${page} processed.`, 'color: #28a745', 'color: inherit');

                // Update files every 5 pages or if it is the last page
                if (page % 5 === 0 || page === END_PAGE) {
                    if (group3to12.length > lastSizes.g1) {
                        await updateRealTimeFile(handle3to12, group3to12);
                        lastSizes.g1 = group3to12.length;
                    }
                    if (group12to36.length > lastSizes.g2) {
                        await updateRealTimeFile(handle12to36, group12to36);
                        lastSizes.g2 = group12to36.length;
                    }
                    if (group36plus.length > lastSizes.g3) {
                        await updateRealTimeFile(handle36plus, group36plus);
                        lastSizes.g3 = group36plus.length;
                    }
                    console.log(`%c[SAVING]%c Files updated on disk (page ${page}).`, 'color: #17a2b8', 'color: inherit');
                }

                errorStage = 0;
                page++;

                // Anti-spam safety delay
                await delay(1500);

            } catch (error) {
                console.error(`%c[ERROR]%c Error on page ${page}:`, 'color: #dc3545', 'color: inherit', error.message);

                if (errorStage === 0) {
                    console.warn(`%c[WAITING]%c Waiting 5 minutes before retrying page ${page}...`, 'color: #fd7e14', 'color: inherit');
                    await delay(mins(5));
                    errorStage = 1;
                    
                } else if (errorStage === 1) {
                    console.warn(`%c[SKIPPING]%c Error again. Skipping page ${page}, moving to the next and waiting 10 minutes...`, 'color: #fd7e14', 'color: inherit');
                    page++;
                    await delay(mins(10));
                    errorStage = 2;
                    
                } else if (errorStage === 2) {
                    console.warn(`%c[SKIPPING]%c Error again. Skipping page ${page}, moving to the next and waiting 30 minutes...`, 'color: #dc3545', 'color: inherit');
                    page++;
                    await delay(mins(30));
                    errorStage = 3;
                    
                } else {
                    console.warn(`%c[SKIPPING]%c Cyclical error. Skipping page ${page}, moving to the next and waiting 10 minutes...`, 'color: #fd7e14', 'color: inherit');
                    page++;
                    await delay(mins(10));
                }
            }
        }

        console.log(`%c[FINISH]%c All pages processed! Files are up to date and located in your selected folder.`, 'font-weight: bold; color: #28a745', 'color: inherit');
    }

    // Create a button to bypass File System API restriction
    const uiOverlay = document.createElement('div');
    uiOverlay.style.cssText = 'position:fixed; top:0; left:0; width:100vw; height:100vh; background:rgba(0,0,0,0.85); z-index:999999; display:flex; flex-direction:column; justify-content:center; align-items:center; color:#fff; font-family:sans-serif;';
    
    const startBtn = document.createElement('button');
    startBtn.textContent = 'START PARSING (SELECT FOLDER)';
    startBtn.style.cssText = 'padding:20px 40px; font-size:24px; font-weight:bold; cursor:pointer; background:#28a745; color:#fff; border:none; border-radius:10px; box-shadow: 0 4px 15px rgba(40,167,69,0.5); transition: 0.2s;';
    startBtn.onmouseover = () => startBtn.style.transform = 'scale(1.05)';
    startBtn.onmouseout = () => startBtn.style.transform = 'scale(1)';
    
    const infoText = document.createElement('p');
    infoText.innerHTML = 'Click the button and select an <b>empty folder</b> on your PC.<br>The script will create 3 files in it and update them in real time.';
    infoText.style.cssText = 'margin-top:20px; font-size:16px; text-align:center; line-height:1.5; color:#ccc;';

    uiOverlay.appendChild(startBtn);
    uiOverlay.appendChild(infoText);
    document.body.appendChild(uiOverlay);

    startBtn.addEventListener('click', async () => {
        document.body.removeChild(uiOverlay);
        await startProcess();
    });

})();