您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Различные полезности для 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(); })();