FP Tools

Различные полезности для FunPay: копирование лотов, замена пустого чата на активные лоты, логирование сообщений в Discord

// ==UserScript==
// @name         FP Tools
// @namespace    https://funpay.com/
// @version      1.6
// @description  Различные полезности для FunPay: копирование лотов, замена пустого чата на активные лоты, логирование сообщений в Discord
// @author       Your Name
// @match        https://funpay.com/*
// @grant        GM_xmlhttpRequest
// @grant        GM_addStyle
// @run-at       document-end
// ==/UserScript==

(function() {
    'use strict';

    function createElement(tag, attributes = {}, styles = {}, innerHTML = '') {
        const element = document.createElement(tag);
        for (const [key, value] of Object.entries(attributes)) {
            element.setAttribute(key, value);
        }
        for (const [key, value] of Object.entries(styles)) {
            element.style[key] = value;
        }
        element.innerHTML = innerHTML;
        return element;
    }

    function sendToDiscordWebhook(node) {
        const userName = node.querySelector('.media-user-name').textContent.trim();
        const messageText = node.querySelector('.contact-item-message').textContent.trim();
        const avatarUrl = node.querySelector('.avatar-photo').style.backgroundImage.slice(5, -2);
        const webhookUrl = localStorage.getItem('discordWebhookUrl');

        if (!webhookUrl) {
            console.error('uRL not set');
            return;
        }

        const payload = {
            username: userName,
            avatar_url: avatarUrl,
            embeds: [{
                description: messageText,
                color: 0x00FF00
            }]
        };

        fetch(webhookUrl, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify(payload)
        })
            .then(response => {
            if (!response.ok) {
                console.error('failed to send message to discord');
            }
        })
            .catch(error => {
            console.error('error sending message to ds:', error);
        });
    }

    const cloneButton = createElement('button', { class: 'btn btn-default' }, { marginLeft: '10px' }, 'Копировать');

    const header = Array.from(document.querySelectorAll('h1.page-header.page-header-no-hr'))
    .find(h1 => h1.textContent.includes('Редактирование предложения'));

    if (header) {
        header.parentNode.insertBefore(cloneButton, header.nextSibling);
    }

    const popupMenu = createElement('div', {}, {
        display: 'none',
        position: 'fixed',
        top: '50%',
        left: '50%',
        transform: 'translate(-50%, -50%)',
        backgroundColor: 'gray',
        border: '1px solid black',
        padding: '20px',
        zIndex: '10000'
    }, `
        <button id="fullClone" class="btn btn-primary">Скопировать полностью</button>
        <button id="changeCategoryClone" class="btn btn-primary">Поменять категорию и скопировать</button>
        <button id="closePopup" class="btn btn-default">Закрыть</button>
    `);
    document.body.appendChild(popupMenu);

    const navBar = document.querySelector('.nav.navbar-nav.navbar-right.logged');
    const toolsMenu = createElement('li', {}, {}, `
        <a style="font-weight: bold; cursor: pointer; user-select: none;" id="fpToolsButton">FP Tools</a>
    `);
    navBar.appendChild(toolsMenu);

    const styles = `
    .fp-tools-popup {
        display: none;
        position: fixed;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        background: rgba(20, 20, 20, 0.8);
        backdrop-filter: blur(20px);
        border-radius: 30px;
        box-shadow: 0 0 100px rgba(149, 0, 255, 0.3), 0 0 30px rgba(0, 247, 255, 0.5);
        padding: 40px;
        z-index: 10000;
        font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
        max-width: 500px;
        width: 100%;
        color: #fff;
        transition: all 0.5s cubic-bezier(0.68, -0.55, 0.27, 1.55);
    }
    .fp-tools-popup.active {
        display: block;
        animation: popIn 0.7s cubic-bezier(0.26, 0.53, 0.74, 1.48);
    }
    @keyframes popIn {
        0% { opacity: 0; transform: translate(-50%, -60%) scale(0.5); }
        100% { opacity: 1; transform: translate(-50%, -50%) scale(1); }
    }
    .fp-tools-popup h2 {
        margin: 0 0 30px;
        font-size: 32px;
        font-weight: 700;
        color: #fff;
        text-shadow: 0 0 10px rgba(255, 255, 255, 0.5);
        text-align: center;
        letter-spacing: 2px;
    }
    .fp-tools-popup .close-btn {
        position: absolute;
        top: 20px;
        right: 20px;
        background: rgba(255, 255, 255, 0.1);
        border: none;
        color: #fff;
        font-size: 24px;
        width: 40px;
        height: 40px;
        border-radius: 50%;
        cursor: pointer;
        transition: all 0.3s ease;
        display: flex;
        align-items: center;
        justify-content: center;
    }
    .fp-tools-popup .close-btn:hover {
        background: rgba(255, 255, 255, 0.2);
        transform: scale(1.1);
    }
    .fp-tools-popup .close-btn::after {
        content: '×';
        display: block;
        transform: translateY(-1px);
    }
    .fp-tools-popup label {
        display: flex;
        align-items: center;
        margin-bottom: 20px;
        font-size: 18px;
        cursor: pointer;
    }
    .fp-tools-popup input[type="checkbox"] {
        appearance: none;
        -webkit-appearance: none;
        width: 24px;
        height: 24px;
        border-radius: 5px;
        margin-right: 15px;
        background: rgba(255, 255, 255, 0.1);
        position: relative;
        cursor: pointer;
        transition: all 0.3s ease;
    }
    .fp-tools-popup input[type="checkbox"]:checked {
        background: linear-gradient(45deg, #00C9FF, #92FE9D);
    }
    .fp-tools-popup input[type="checkbox"]::after {
        content: '✓';
        position: absolute;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        font-size: 16px;
        color: #fff;
        opacity: 0;
        transition: opacity 0.2s ease;
    }
    .fp-tools-popup input[type="checkbox"]:checked::after {
        opacity: 1;
    }
    .fp-tools-popup input[type="text"] {
        width: 100%;
        padding: 15px;
        margin-bottom: 25px;
        border: none;
        border-radius: 15px;
        background: rgba(255, 255, 255, 0.1);
        color: #fff;
        font-size: 16px;
        transition: all 0.3s ease;
    }
    .fp-tools-popup input[type="text"]:focus {
        outline: none;
        box-shadow: 0 0 0 3px rgba(0, 247, 255, 0.5);
    }
    .fp-tools-popup input[type="text"]:disabled {
        opacity: 0.5;
        cursor: not-allowed;
    }
    .fp-tools-popup input[type="text"]:disabled::before {
        content: '';
        position: absolute;
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;
        background: rgba(0, 0, 0, 0.5);
        border-radius: 15px;
        z-index: 1;
    }
    .fp-tools-popup button {
        background: linear-gradient(45deg, #FF6B6B, #6B66FF);
        color: white;
        border: none;
        padding: 15px 30px;
        font-size: 18px;
        font-weight: bold;
        cursor: pointer;
        border-radius: 50px;
        transition: all 0.3s ease;
        text-transform: uppercase;
        letter-spacing: 2px;
        box-shadow: 0 10px 20px rgba(0, 0, 0, 0.2);
        position: relative;
        overflow: hidden;
    }
    .fp-tools-popup button::before {
        content: '';
        position: absolute;
        top: -50%;
        left: -50%;
        width: 200%;
        height: 200%;
        background: radial-gradient(circle, rgba(255,255,255,0.3) 0%, rgba(255,255,255,0) 80%);
        transform: scale(0);
        transition: transform 0.6s ease-out;
    }
    .fp-tools-popup button:hover::before {
        transform: scale(1);
    }
    .fp-tools-popup button:hover {
        transform: translateY(-5px);
        box-shadow: 0 15px 30px rgba(0, 0, 0, 0.3);
    }
    .fp-tools-popup button:active {
        transform: translateY(2px);
    }
`;

    const styleElement = document.createElement('style');
    styleElement.textContent = styles;
    document.head.appendChild(styleElement);

    const toolsPopup = document.createElement('div');
    toolsPopup.className = 'fp-tools-popup';
    toolsPopup.innerHTML = `
    <h2>FP Tools</h2>
    <button class="close-btn" aria-label="Закрыть"></button>
    <div>
        <label>
            <input type="checkbox" id="logToDiscordCheckbox">
            Логирование сообщений в Discord
        </label>
    </div>
    <input type="text" id="discordWebhookUrl" placeholder="Вставьте ссылку на вебхук" disabled>
    <button id="saveSettings">Сохранить</button>
`;

    document.body.appendChild(toolsPopup);

    document.getElementById('fpToolsButton').addEventListener('click', () => {
        toolsPopup.classList.add('active');
    });

    document.querySelector('.fp-tools-popup .close-btn').addEventListener('click', () => {
        toolsPopup.classList.remove('active');
    });

    document.getElementById('logToDiscordCheckbox').addEventListener('change', (event) => {
        const webhookInput = document.getElementById('discordWebhookUrl');
        webhookInput.disabled = !event.target.checked;
        if (event.target.checked) {
            webhookInput.focus();
        }
    });

    document.getElementById('saveSettings').addEventListener('click', () => {
        const webhookUrl = document.getElementById('discordWebhookUrl').value;
        const logToDiscord = document.getElementById('logToDiscordCheckbox').checked;
        localStorage.setItem('discordWebhookUrl', webhookUrl);
        localStorage.setItem('logToDiscord', logToDiscord);
        toolsPopup.classList.remove('active');
        showNotification('Настройки сохранены!');
    });

    function showNotification(message) {
        const notification = document.createElement('div');
        notification.textContent = message;
        notification.style.cssText = `
        position: fixed;
        bottom: 30px;
        right: 30px;
        background: linear-gradient(45deg, #00C9FF, #92FE9D);
        color: white;
        padding: 20px 30px;
        border-radius: 50px;
        font-size: 18px;
        font-weight: bold;
        box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
        animation: slideIn 0.5s forwards, fadeOut 0.5s 2.5s forwards;
    `;
        document.body.appendChild(notification);

        setTimeout(() => {
            document.body.removeChild(notification);
        }, 3000);
    }

    const keyframes = `
    @keyframes slideIn {
        from { transform: translateX(100%); opacity: 0; }
        to { transform: translateX(0); opacity: 1; }
    }
    @keyframes fadeOut {
        from { opacity: 1; }
        to { opacity: 0; }
    }
`;
    const styleSheet = document.createElement("style");
    styleSheet.type = "text/css";
    styleSheet.innerText = keyframes;
    document.head.appendChild(styleSheet);

    const savedWebhookUrl = localStorage.getItem('discordWebhookUrl');
    const savedLogToDiscord = localStorage.getItem('logToDiscord') === 'true';

    if (savedWebhookUrl) {
        document.getElementById('discordWebhookUrl').value = savedWebhookUrl;
    }
    document.getElementById('logToDiscordCheckbox').checked = savedLogToDiscord;
    document.getElementById('discordWebhookUrl').disabled = !savedLogToDiscord;

    function submitForm(formData) {
        return new Promise((resolve, reject) => {
            const nodeId = new URLSearchParams(window.location.search).get('node');
            formData.set('node_id', nodeId);
            formData.set('offer_id', '0');

            const data = {};
            formData.forEach((value, key) => {
                data[key] = value;
            });

            GM_xmlhttpRequest({
                method: 'POST',
                url: 'https://funpay.com/lots/offerSave',
                headers: {
                    'Content-Type': 'application/x-www-form-urlencoded'
                },
                data: new URLSearchParams(data).toString(),
                onload: (response) => {
                    if (response.status === 200) {
                        showNotification('Лот успешно продублирован!');
                        resolve();
                    } else {
                        console.error('Ошибка при копировании лота', response);
                        reject('Ошибка при копировании лота');
                    }
                },
                onerror: (error) => {
                    console.error('Ошибка при выполнении запроса', error);
                    reject('Ошибка при выполнении запроса');
                }
            });
        });
    }

    cloneButton.addEventListener('click', () => {
        popupMenu.style.display = 'block';
    });

    document.getElementById('fullClone').addEventListener('click', () => {
        popupMenu.style.display = 'none';
        const form = document.querySelector('form.form-offer-editor');
        if (!form) {
            console.error('Форма не найдена');
            return;
        }
        const formData = new FormData(form);
        submitForm(formData);
    });

    document.getElementById('changeCategoryClone').addEventListener('click', () => {
        popupMenu.style.display = 'none';
        const selects = document.querySelectorAll('select.form-control.lot-field-input, select.form-control[name="server_id"]');
        const categoryData = {};
        selects.forEach(select => {
            const label = select.previousElementSibling ? select.previousElementSibling.textContent.trim() : 'Категория';
            if (!categoryData[label]) {
                categoryData[label] = [];
            }
            select.querySelectorAll('option').forEach(option => {
                categoryData[label].push({
                    value: option.value,
                    text: option.textContent.trim()
                });
            });
        });
        const categoryMenu = createElement('div', {}, {
            display: 'none',
            position: 'fixed',
            top: '50%',
            left: '50%',
            transform: 'translate(-50%, -50%)',
            backgroundColor: 'gray',
            border: '1px solid black',
            padding: '20px',
            zIndex: '10000'
        });
        let htmlContent = '';
        for (const label in categoryData) {
            htmlContent += `<div>`;
            htmlContent += `<label><input type="checkbox" id="${label}SelectAll"> Все</label>`;
            htmlContent += `<label for="${label}Select">${label}:</label>`;
            htmlContent += `<select id="${label}Select" class="form-control" multiple>`;
            categoryData[label].forEach(option => {
                htmlContent += `<option value="${option.value}">${option.text}</option>`;
            });
            htmlContent += `</select>`;
            htmlContent += `</div>`;
        }
        htmlContent += `<button id="copyWithCategory" class="btn btn-primary">Копировать</button>`;
        htmlContent += `<button id="closeCategoryMenu" class="btn btn-default">Закрыть</button>`;
        categoryMenu.innerHTML = htmlContent;
        document.body.appendChild(categoryMenu);
        categoryMenu.style.display = 'block';

        // Добавляем обработчики для чекбоксов "Все"
        for (const label in categoryData) {
            document.getElementById(`${label}SelectAll`).addEventListener('change', (event) => {
                const select = document.getElementById(`${label}Select`);
                const options = select.options;
                for (let i = 0; i < options.length; i++) {
                    options[i].selected = event.target.checked;
                }
            });
        }

        document.getElementById('copyWithCategory').addEventListener('click', async () => {
            categoryMenu.style.display = 'none';
            const form = document.querySelector('form.form-offer-editor');
            if (!form) {
                console.error('Форма не найдена');
                return;
            }
            const selectedCategories = [];
            for (const label in categoryData) {
                const selectedOptions = Array.from(document.getElementById(`${label}Select`).selectedOptions)
                    .map(option => option.value);
                if (selectedOptions.length > 0) {
                    selectedCategories.push({
                        label: label,
                        selectedOptions: selectedOptions
                    });
                }
            }
            for (const category of selectedCategories) {
                for (const option of category.selectedOptions) {
                    const clonedFormData = new FormData(form);
                    if (category.label === 'Категория') {
                        clonedFormData.set('lot_category', option);
                    } else {
                        clonedFormData.set('server_id', option);
                    }
                    await submitForm(clonedFormData);
                    await new Promise(resolve => setTimeout(resolve, 1000));
                }
            }
            document.body.removeChild(categoryMenu);
        });

        document.getElementById('closeCategoryMenu').addEventListener('click', () => {
            document.body.removeChild(categoryMenu);
        });
    });

    document.getElementById('closePopup').addEventListener('click', () => {
        popupMenu.style.display = 'none';
    });

    function replaceEmptyChatWithActiveOrders() {
        const emptyChat = document.querySelector('.chat-empty');
        if (emptyChat) {
            GM_xmlhttpRequest({
                method: 'GET',
                url: 'https://funpay.com/orders/trade',
                onload: (response) => {
                    if (response.status === 200) {
                        const parser = new DOMParser();
                        const doc = parser.parseFromString(response.responseText, 'text/html');
                        const activeOrders = doc.querySelectorAll('.tc-item.info');
                        const activeOrdersContainer = createElement('div', { class: 'active-orders-container' }, {
                            width: '100%',
                            padding: '10px',
                            boxSizing: 'border-box',
                            position: 'absolute',
                            top: '10px',
                            left: '10px',
                            fontSize: '0.67em'
                        });

                        activeOrders.forEach(order => {
                            const statusElement = order.querySelector('.tc-status');
                            if (statusElement && statusElement.textContent.trim() === 'Оплачен') {
                                const orderElement = createElement('a', { href: order.href }, {
                                    display: 'block',
                                    marginBottom: '10px',
                                    padding: '5px',
                                    border: '1px solid #ddd',
                                    borderRadius: '5px',
                                    textDecoration: 'none',
                                    color: 'inherit',
                                    transition: 'all 0.3s ease'
                                });

                                orderElement.onmouseover = () => {
                                    orderElement.style.backgroundColor = '#f0f0f0';
                                    orderElement.style.transform = 'scale(1.03)';
                                    orderElement.style.boxShadow = '0 2px 5px rgba(0,0,0,0.1)';
                                };
                                orderElement.onmouseout = () => {
                                    orderElement.style.backgroundColor = '';
                                    orderElement.style.transform = '';
                                    orderElement.style.boxShadow = '';
                                };

                                const dateElement = order.querySelector('.tc-date-time');
                                if (dateElement) {
                                    const fullDate = dateElement.textContent.trim();
                                    const dateParts = fullDate.split(',');
                                    if (dateParts.length > 0) {
                                        const shortDate = dateParts[0].trim();
                                        orderElement.innerHTML += `<div style="font-weight: bold;">${shortDate}</div>`;
                                    }
                                }

                                const descElement = order.querySelector('.order-desc');
                                if (descElement) {
                                    const descText = descElement.textContent.trim();
                                    orderElement.innerHTML += `<div style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">${descText}</div>`;
                                }

                                const priceElement = order.querySelector('.tc-price');
                                if (priceElement) {
                                    orderElement.innerHTML += `<div style="color: green;">${priceElement.textContent.trim()}</div>`;
                                }

                                activeOrdersContainer.appendChild(orderElement);
                            }
                        });

                        if (activeOrdersContainer.children.length > 0) {
                            emptyChat.innerHTML = '';
                            emptyChat.appendChild(activeOrdersContainer);
                            emptyChat.style.padding = '0';
                        }
                    } else {
                        console.error('ошибка при загрузке активных заказов', response);
                    }
                },
                onerror: (error) => {
                    console.error('ошибка при выполнении запроса активных заказов', error);
                }
            });
        }
    }

    replaceEmptyChatWithActiveOrders();

    function logNewMessagesToDiscord() {
        const unreadMessages = document.querySelectorAll('.contact-item.unread');

        unreadMessages.forEach(message => {
            const messageId = message.getAttribute('data-id');
            const isAlreadySent = localStorage.getItem(`discordSent_${messageId}`);

            if (!isAlreadySent) {
                sendToDiscordWebhook(message);
                localStorage.setItem(`discordSent_${messageId}`, true);
            }
        });
    }

    logNewMessagesToDiscord();

})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址