Vinted Search Saver

Saves Vinted searches for https://github.com/Fuyucch1/Vinted-Notifications/

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Vinted Search Saver
// @namespace    http://tampermonkey.net/
// @version      1.1
// @description  Saves Vinted searches for https://github.com/Fuyucch1/Vinted-Notifications/
// @author       Quai
// @match        https://*.vinted.de/*
// @match        https://*.vinted.fr/*
// @match        https://*.vinted.be/*
// @match        https://*.vinted.nl/*
// @match        https://*.vinted.it/*
// @match        https://*.vinted.pl/*
// @match        https://*.vinted.cz/*
// @match        https://*.vinted.es/*
// @match        https://*.vinted.se/*
// @match        https://*.vinted.com/*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';
    
    // ===== USER CONFIGURATION =====
    // Change these settings to match your server configuration
    // If you're running the server locally, leave the default settings
    // If you're running the server on another machine or custom port, update accordingly
    const SERVER_CONFIG = {
        host: 'localhost',  // Change to your server IP or hostname if not running locally
        port: 8000,        // Change if using a different port
        protocol: 'http'   // Change to 'https' if your server uses SSL
    };
    
    // Construct the base URL from configuration
    const SERVER_BASE_URL = `${SERVER_CONFIG.protocol}://${SERVER_CONFIG.host}:${SERVER_CONFIG.port}`;

    const style = document.createElement('style');
    style.textContent = `
        .search-saver-notification {
            position: fixed;
            top: 20px;
            right: 20px;
            background-color: #4CAF50;
            color: white;
            padding: 15px;
            border-radius: 4px;
            z-index: 10000;
            display: none;
            box-shadow: 0 2px 5px rgba(0,0,0,0.2);
        }

        .floating-save-button {
            position: fixed;
            bottom: 30px;
            right: 30px;
            width: 56px;
            height: 56px;
            border-radius: 50%;
            background-color: #09B1BA;
            color: white;
            border: none;
            cursor: pointer;
            box-shadow: 0 4px 8px rgba(0,0,0,0.2);
            display: flex;
            align-items: center;
            justify-content: center;
            transition: transform 0.2s, background-color 0.2s;
            z-index: 10000;
        }

        .floating-save-button.disabled {
            background-color: #d9534f;
            cursor: not-allowed;
            opacity: 0.8;
        }

        .floating-save-button.disabled svg {
            opacity: 0.7;
            position: relative;
        }

        .floating-save-button.disabled svg::after {
            content: '';
            position: absolute;
            left: 0;
            top: 50%;
            width: 100%;
            height: 2px;
            background-color: currentColor;
            transform: rotate(-45deg);
        }

        .floating-save-button:hover {
            transform: scale(1.1);
            background-color: #0A9DA5;
        }

        .floating-save-button.disabled:hover {
            background-color: #c9302c;
            transform: scale(1.1);
        }

        .save-modal {
            position: fixed;
            bottom: 100px;
            right: 30px;
            background-color: white;
            padding: 20px;
            border-radius: 8px;
            box-shadow: 0 4px 12px rgba(0,0,0,0.15);
            display: none;
            z-index: 10000;
            width: 300px;
        }

        .save-modal input {
            width: 100%;
            padding: 8px;
            border: 1px solid #ddd;
            border-radius: 4px;
            font-size: 14px;
            margin-bottom: 10px;
        }

        .save-modal button {
            width: 100%;
            padding: 8px;
            border: none;
            border-radius: 4px;
            background-color: #09B1BA;
            color: white;
            cursor: pointer;
            font-weight: 600;
            transition: background-color 0.2s;
        }

        .save-modal button:hover {
            background-color: #0A9DA5;
        }
    `;
    document.head.appendChild(style);

    const notification = document.createElement('div');
    notification.className = 'search-saver-notification';
    document.body.appendChild(notification);

    function showNotification(message) {
        notification.textContent = message;
        notification.style.display = 'block';
        setTimeout(() => {
            notification.style.display = 'none';
        }, 3000);
    }


    function isValidSearchUrl(url) {
        try {
            const urlObj = new URL(url);
            return urlObj.pathname.includes('/catalog') || urlObj.searchParams.has('search_text');
        } catch {
            return false;
        }
    }

    async function saveSearch(name) {
        const currentUrl = window.location.href;

        if (!isValidSearchUrl(currentUrl)) {
            showNotification('❌ This page is not a valid Vinted search query');
            return;
        }

        try {
            const response = await fetch(`${SERVER_BASE_URL}/add_query`, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/x-www-form-urlencoded',
                },
                body: `query=${encodeURIComponent(currentUrl)}&name=${encodeURIComponent(name)}`,
            });

            if (response.ok) {
                showNotification('✅ Search successfully saved!');
            } else {
                showNotification('❌ Error saving the search');
            }
        } catch (error) {
            showNotification(`❌ Connection error to backend (${SERVER_BASE_URL})`);
            console.error('Error:', error);
        }
    }

    function createSaveInterface() {

        const floatingButton = document.createElement('button');
        floatingButton.className = 'floating-save-button';

        if (!isValidSearchUrl(window.location.href)) {
            floatingButton.classList.add('disabled');
        }
        floatingButton.innerHTML = '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 21l-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z"></path><line x1="2" y1="4" x2="22" y2="20" stroke-width="2" class="strike-through" style="display: none; stroke: currentColor; stroke-linecap: round;"/></svg>';

        const updateStrikeThrough = () => {
            const strikeLine = floatingButton.querySelector('.strike-through');
            strikeLine.style.display = floatingButton.classList.contains('disabled') ? 'block' : 'none';
        };

        updateStrikeThrough();

        const saveModal = document.createElement('div');
        saveModal.className = 'save-modal';

        const nameInput = document.createElement('input');
        nameInput.type = 'text';
        nameInput.placeholder = 'Search name';

        const saveButton = document.createElement('button');
        saveButton.textContent = 'Save';

        saveModal.appendChild(nameInput);
        saveModal.appendChild(saveButton);

        floatingButton.addEventListener('click', () => {
            const currentUrl = window.location.href;
            if (!isValidSearchUrl(currentUrl)) {
                showNotification('❌ This page is not a valid Vinted search query');
                return;
            }
            saveModal.style.display = saveModal.style.display === 'none' ? 'block' : 'none';
        });

        const observer = new MutationObserver(() => {
            if (!isValidSearchUrl(window.location.href)) {
                floatingButton.classList.add('disabled');
            } else {
                floatingButton.classList.remove('disabled');
            }
            updateStrikeThrough();
        });

        observer.observe(document.body, { childList: true, subtree: true });

        saveButton.addEventListener('click', () => {
            const name = nameInput.value.trim();
            if (!name) {
                showNotification('❌ Please enter a name for the search');
                return;
            }
            saveSearch(name);
            nameInput.value = '';
            saveModal.style.display = 'none';
        });

        document.addEventListener('click', (e) => {
            if (!saveModal.contains(e.target) && !floatingButton.contains(e.target)) {
                saveModal.style.display = 'none';
            }
        });

        document.body.appendChild(floatingButton);
        document.body.appendChild(saveModal);
    }

    createSaveInterface();
})();