- // ==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();
-
- })();