Загрузка...

Utility Sorting participants of the draw by the date of participation

Thread in Extentions created by Архистратиг Mar 26, 2025. 175 views

  1. Архистратиг
    Архистратиг Topic starter Mar 26, 2025 аниме малышка продается - https://lolz.live/threads/7421153/ 16,602 Jul 26, 2020
    по просьбе klopybrittan сделал

    1. B Tampermonkey суешь скрипт

    Code

    // ==UserScript==
    // @name Lolz.live Contest Sorter (Fixed UI)
    // @namespace [URL]http://tampermonkey.net/[/URL]
    // @version 1.5
    // @description Надежный сбор и сортировка участников конкурса с исправленным интерфейсом
    // @author Your Name
    // @match [URL]https://lolz.live/threads/*/contest-users*[/URL]
    // @grant GM_xmlhttpRequest
    // @grant GM_addStyle
    // @connect lolz.live
    // ==/UserScript==

    (function() {
    'use strict';

    // Стили для наших элементов
    GM_addStyle(`
    #contestSorterBtn {
    background: #4CAF50 !important;
    color: white !important;
    border: none !important;
    padding: 10px 15px !important;
    border-radius: 4px !important;
    font-weight: bold !important;
    cursor: pointer !important;
    margin: 10px !important;
    position: fixed !important;
    top: 20px !important;
    right: 20px !important;
    z-index: 9999 !important;
    }
    #contestSorterStatus {
    position: fixed !important;
    top: 60px !important;
    right: 20px !important;
    background: white !important;
    padding: 10px !important;
    border-radius: 4px !important;
    box-shadow: 0 0 10px rgba(0,0,0,0.2) !important;
    z-index: 9998 !important;
    max-width: 300px !important;
    }
    #contestSorterResults {
    position: fixed !important;
    top: 100px !important;
    right: 20px !important;
    bottom: 20px !important;
    width: 550px !important;
    background: white !important;
    overflow-y: auto !important;
    padding: 20px !important;
    box-shadow: 0 0 20px rgba(0,0,0,0.3) !important;
    z-index: 9997 !important;
    border-radius: 5px !important;
    }
    .contestSorterControlBtn {
    position: fixed !important;
    top: 120px !important;
    right: 30px !important;
    background: #f44336 !important;
    color: white !important;
    border: none !important;
    padding: 5px 10px !important;
    border-radius: 4px !important;
    cursor: pointer !important;
    z-index: 9999 !important;
    }
    .contestSorterCopyBtn {
    right: 120px !important;
    background: #2196F3 !important;
    }
    `);

    // Проверяем и создаем интерфейс каждые 500мс пока не найдем нужные элементы
    const initInterval = setInterval(() => {
    if (document.querySelector('li.primaryContent.memberListItem')) {
    clearInterval(initInterval);
    createInterface();
    }
    }, 500);

    // Также создаем интерфейс при изменениях DOM (на случай SPA-навигации)
    const observer = new MutationObserver(() => {
    if (!document.getElementById('contestSorterBtn') &&
    document.querySelector('li.primaryContent.memberListItem')) {
    createInterface();
    }
    });
    observer.observe(document.body, { childList: true, subtree: true });

    function createInterface() {
    // Если кнопка уже есть - выходим
    if (document.getElementById('contestSorterBtn')) return;

    // Создаем кнопку
    const btn = document.createElement('button');
    btn.id = 'contestSorterBtn';
    btn.textContent = 'Сортировать участников';
    document.body.appendChild(btn);

    // Создаем статус
    const status = document.createElement('div');
    status.id = 'contestSorterStatus';
    document.body.appendChild(status);

    btn.addEventListener('click', startProcessing);
    }

    async function startProcessing() {
    const btn = document.getElementById('contestSorterBtn');
    const status = document.getElementById('contestSorterStatus');

    btn.disabled = true;
    status.innerHTML = 'Начинаем сбор участников...';

    try {
    const allUsers = await getAllUsers();

    if (allUsers.length === 0) {
    status.innerHTML = 'Участники не найдены. Проверьте структуру страницы.';
    return;
    }

    // Удаление дубликатов по ID пользователя и времени
    const uniqueUsers = removeDuplicates(allUsers);

    if (uniqueUsers.length !== allUsers.length) {
    status.innerHTML += `<br>Обнаружено и удалено ${allUsers.length - uniqueUsers.length} дубликатов.`;
    }

    // Сортировка по времени с дополнительной проверкой
    uniqueUsers.sort((a, b) => {
    if (a.timestamp !== b.timestamp) return a.timestamp - b.timestamp;
    if (a.page !== b.page) return a.page - b.page;
    return a.pagePosition - b.pagePosition;
    });

    showResults(uniqueUsers);

    } catch (error) {
    console.error('Ошибка:', error);
    status.innerHTML = 'Ошибка: ' + error.message;
    } finally {
    btn.disabled = false;
    }
    }

    function removeDuplicates(users) {
    const seen = new Set();
    return users.filter(user => {
    const key = `${user.userId}_${user.timestamp}_${user.page}_${user.pagePosition}`;
    return seen.has(key) ? false : (seen.add(key), true);
    });
    }

    async function getAllUsers() {
    const status = document.getElementById('contestSorterStatus');
    const users = [];
    let page = 1;
    let hasMore = true;
    const baseUrl = window.location.href.split('?')[0].replace(/\/contest-users$/, '');

    // Сначала собираем с текущей страницы
    const currentPageUsers = getUsersFromPage(document, 1);
    users.push(...currentPageUsers);
    status.innerHTML = `Собрано участников: ${users.length}. Проверяем другие страницы...`;

    // Затем проверяем другие страницы
    while (hasMore) {
    page++;
    const nextPageUrl = `${baseUrl}/contest-users?page=${page}`;
    status.innerHTML = `Проверяем страницу ${page}... (Собрано: ${users.length})`;

    try {
    const pageUsers = await getUsersFromUrl(nextPageUrl, page);
    if (pageUsers.length === 0) {
    hasMore = false;
    } else {
    users.push(...pageUsers);
    status.innerHTML = `Страница ${page} обработана. Участников: ${users.length}.`;
    await new Promise(resolve => setTimeout(resolve, 800));
    }
    } catch (error) {
    console.error(`Ошибка при обработке страницы ${page}:`, error);
    hasMore = false;
    }
    }

    return users;
    }

    async function getUsersFromUrl(url, pageNumber) {
    return new Promise((resolve, reject) => {
    GM_xmlhttpRequest({
    method: "GET",
    url: url,
    onload: function(response) {
    if (response.status === 200) {
    const doc = new DOMParser().parseFromString(response.responseText, 'text/html');
    resolve(getUsersFromPage(doc, pageNumber));
    } else if (response.status === 404) {
    resolve([]);
    } else {
    reject(new Error(`HTTP error! status: ${response.status}`));
    }
    },
    onerror: function(error) {
    reject(error);
    },
    timeout: 10000
    });
    });
    }

    function getUsersFromPage(doc, pageNumber) {
    const items = Array.from(doc.querySelectorAll('li.primaryContent.memberListItem'));
    return items.map((item, index) => {
    const timeElement = item.querySelector('.DateTime');
    const usernameElement = item.querySelector('.username span');
    const userId = item.querySelector('.username')?.getAttribute('data-user-id') || '0';
    const timestamp = timeElement ? parseInt(timeElement.dataset.time) : 0;

    return {
    html: item.outerHTML,
    timestamp: isNaN(timestamp) ? 0 : timestamp,
    username: usernameElement ? usernameElement.textContent.trim() : 'Без имени',
    userId: userId,
    position: (pageNumber - 1) * 20 + index,
    page: pageNumber,
    pagePosition: index
    };
    });
    }

    function showResults(users) {
    const status = document.getElementById('contestSorterStatus');

    // Удаляем старые результаты
    removeElement('contestSorterResults');
    removeElement('contestSorterCloseBtn');
    removeElement('contestSorterCopyBtn');

    status.innerHTML = `Найдено уникальных участников: ${users.length}. Создаем список...`;

    // Создаем контейнер для результатов
    const resultsContainer = document.createElement('div');
    resultsContainer.id = 'contestSorterResults';
    document.body.appendChild(resultsContainer);

    // Добавляем заголовок
    const title = document.createElement('h3');
    title.textContent = `Отсортированные участники (${users.length})`;
    title.style.marginTop = '0';
    resultsContainer.appendChild(title);

    // Добавляем статистику
    const stats = document.createElement('div');
    stats.style.marginBottom = '15px';
    stats.style.padding = '10px';
    stats.style.backgroundColor = '#f5f5f5';
    stats.style.borderRadius = '4px';
    stats.innerHTML = `
    <div><strong>Первый участник:</strong> ${users[0].username} (${formatDate(users[0].timestamp)})</div>
    <div><strong>Последний участник:</strong> ${users[users.length-1].username} (${formatDate(users[users.length-1].timestamp)})</div>
    <div><strong>Всего страниц:</strong> ${users[users.length-1].page}</div>
    `;
    resultsContainer.appendChild(stats);

    // Добавляем список
    const list = document.createElement('div');
    list.style.marginTop = '10px';

    users.forEach((user, index) => {
    const userElement = document.createElement('div');
    userElement.style.marginBottom = '15px';
    userElement.style.padding = '10px';
    userElement.style.border = '1px solid #eee';
    userElement.style.borderRadius = '4px';
    userElement.style.position = 'relative';
    userElement.style.backgroundColor = index % 2 === 0 ? '#fafafa' : 'white';

    const updatedHtml = user.html.replace(/Номер: \d+/, `Номер: ${index + 1}`);

    const numberSpan = document.createElement('span');
    numberSpan.textContent = `${index + 1}. `;
    numberSpan.style.fontWeight = 'bold';
    numberSpan.style.marginRight = '5px';

    const timeSpan = document.createElement('span');
    timeSpan.textContent = formatDate(user.timestamp);
    timeSpan.style.color = '#666';
    timeSpan.style.fontSize = '0.9em';
    timeSpan.style.marginLeft = '10px';

    const pageSpan = document.createElement('span');
    pageSpan.textContent = `(стр. ${user.page}, поз. ${user.pagePosition + 1})`;
    pageSpan.style.color = '#888';
    pageSpan.style.fontSize = '0.8em';
    pageSpan.style.marginLeft = '10px';

    userElement.innerHTML = updatedHtml;
    userElement.prepend(numberSpan);
    userElement.append(timeSpan);
    userElement.append(pageSpan);

    if (index === 0) {
    userElement.style.borderLeft = '4px solid #4CAF50';
    userElement.style.backgroundColor = '#f8fff8';
    } else if (index === users.length - 1) {
    userElement.style.borderLeft = '4px solid #f44336';
    userElement.style.backgroundColor = '#fff8f8';
    }

    list.appendChild(userElement);
    });

    resultsContainer.appendChild(list);

    // Добавляем кнопки управления
    addControlButton('Закрыть', 'contestSorterCloseBtn', () => {
    removeElement('contestSorterResults');
    removeElement('contestSorterCloseBtn');
    removeElement('contestSorterCopyBtn');
    });

    addControlButton('Копировать список', 'contestSorterCopyBtn', () => {
    const textToCopy = users.map((user, index) =>
    `${index + 1}. ${user.username} (ID: ${user.userId}) - ${formatDate(user.timestamp)} (стр. ${user.page}, поз. ${user.pagePosition + 1})`
    ).join('\n');

    navigator.clipboard.writeText(textToCopy).then(() => {
    const btn = document.getElementById('contestSorterCopyBtn');
    const oldText = btn.textContent;
    btn.textContent = 'Скопировано!';
    setTimeout(() => btn.textContent = oldText, 2000);
    });
    }, true);

    status.innerHTML = `Сортировка завершена. Найдено ${users.length} участников.`;
    }

    function addControlButton(text, id, onClick, isCopyBtn = false) {
    const btn = document.createElement('button');
    btn.id = id;
    btn.textContent = text;
    btn.className = isCopyBtn ? 'contestSorterControlBtn contestSorterCopyBtn' : 'contestSorterControlBtn';
    btn.onclick = onClick;
    document.body.appendChild(btn);
    }

    function removeElement(id) {
    const element = document.getElementById(id);
    if (element) element.remove();
    }

    function formatDate(timestamp) {
    if (!timestamp) return 'нет данных';
    const date = new Date(timestamp * 1000);
    return date.toLocaleString('ru-RU', {
    day: '2-digit',
    month: '2-digit',
    year: 'numeric',
    hour: '2-digit',
    minute: '2-digit',
    second: '2-digit'
    });
    }
    })();
    2. Заходишь на страницу с участниками и жмешь кнопку.

    https://lolz.live/threads/8495486/contest-users?page=2

    [IMG]

    3. Ждешь

    [IMG]

    4. Получаешь список по Bремени: сверху самые старые участия - снизу самые ластовые

    [IMG]


    Скрипт иногда багует, но буду дорабатывать

    Пример "копироBания списка"

    [IMG]
     
Loading...
Top