Sniffies Save Profiles

Adds a "Save Profile" button to profiles and a professional GUI to manage saved profiles in a table format. Allows editing the profile title before saving.

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey, το Greasemonkey ή το Violentmonkey για να εγκαταστήσετε αυτόν τον κώδικα.

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey ή το Violentmonkey για να εγκαταστήσετε αυτόν τον κώδικα.

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey ή το Violentmonkey για να εγκαταστήσετε αυτόν τον κώδικα.

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey ή το Userscripts για να εγκαταστήσετε αυτόν τον κώδικα.

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

Θα χρειαστεί να εγκαταστήσετε μια επέκταση διαχείρισης κώδικα χρήστη για να εγκαταστήσετε αυτόν τον κώδικα.

(Έχω ήδη έναν διαχειριστή κώδικα χρήστη, επιτρέψτε μου να τον εγκαταστήσω!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(Έχω ήδη έναν διαχειριστή στυλ χρήστη, επιτρέψτε μου να τον εγκαταστήσω!)

// ==UserScript==
// @name         Sniffies Save Profiles
// @namespace    LiveCamShow.scripts
// @version      1.5
// @description  Adds a "Save Profile" button to profiles and a professional GUI to manage saved profiles in a table format. Allows editing the profile title before saving.
// @author       LiveCamShow
// @match        *://sniffies.com/*
// @license      mit
// ==/UserScript==

(function () {
    'use strict';

    function getSavedProfiles() {
        return JSON.parse(localStorage.getItem('savedProfiles')) || [];
    }

    function setSavedProfiles(profiles) {
        localStorage.setItem('savedProfiles', JSON.stringify(profiles));
    }
    // Save profile data
    const saveProfile = (profileUrl, profileHeader, profileImage) => {
        let savedProfiles = getSavedProfiles();
        const profileExists = savedProfiles.some((profile) => profile.url === profileUrl);

        if (!profileExists) {
            savedProfiles.push({ url: profileUrl, header: profileHeader, image: profileImage });
            setSavedProfiles(savedProfiles);
            alert(`Profile saved: ${profileHeader}`);
        } else {
            alert('Profile already saved!');
        }
    };

    // Display save popup
    const showSavePopup = (profileUrl, profileHeader, profileImage) => {
        // Create the backdrop for click detection
        const backdrop = document.createElement('div');
        Object.assign(backdrop.style, {
            position: 'fixed',
            top: '0',
            left: '0',
            width: '100vw',
            height: '100vh',
            backgroundColor: 'rgba(0, 0, 0, 0.5)',
            zIndex: '9999',
        });


        // Popup Container
        const popupContainer = document.createElement('div');
        Object.assign(popupContainer.style, {
            position: 'fixed',
            top: '50%',
            left: '50%',
            transform: 'translate(-50%, -50%)',
            zIndex: '10000',
            backgroundColor: '#0f1e35',
            color: '#d4d9e4',
            borderTop: '4px solid #4b84e6',
            borderBottom: '4px solid #4b84e6',
            borderRadius: '8px',
            boxShadow: '0 4px 12px rgba(14, 22, 33, .25)',
            padding: '20px',
            width: '300px',
        });

        const titleLabel = document.createElement('label');
        titleLabel.textContent = 'Edit Profile Title:';
        titleLabel.style.display = 'block';
        titleLabel.style.marginBottom = '10px';

        const titleInput = document.createElement('input');
        Object.assign(titleInput, {
            type: 'text',
            value: profileHeader
        });
        Object.assign(titleInput.style, {
            width: '100%',
            marginBottom: '20px',
            padding: '5px',
            border: '1px solid #ccc',
            borderRadius: '4px'
        });

        const saveButton = document.createElement('button');
        saveButton.textContent = 'Save';
        saveButton.style.marginRight = '10px';
        saveButton.addEventListener('click', () => {
            saveProfile(profileUrl, titleInput.value, profileImage);
            backdrop.remove();
        });

        const cancelButton = document.createElement('button');
        cancelButton.textContent = 'Cancel';
        cancelButton.addEventListener('click', () => backdrop.remove());

        popupContainer.append(titleLabel, titleInput, saveButton, cancelButton);
        backdrop.appendChild(popupContainer);
        document.body.appendChild(backdrop);

        // Close popup on clicking outside
        backdrop.addEventListener('click', (e) => {
            if (e.target === backdrop) {
                backdrop.remove();
            }
        });
    };


    // Open Saved Profiles GUI
    const openSavedProfilesGUI = () => {
        const savedProfiles =  getSavedProfiles();

        // Create a backdrop to capture outside clicks
        const backdrop = document.createElement('div');
        Object.assign(backdrop.style, {
            position: 'fixed',
            top: '0',
            left: '0',
            width: '100%',
            height: '100%',
            backgroundColor: 'rgba(0, 0, 0, 0.5)',
            zIndex: '9999',
        });

        backdrop.addEventListener('click', (e) => {
            if (e.target === backdrop) {
                backdrop.remove();
            }
        });



        const guiContainer = document.createElement('div');
        Object.assign(guiContainer.style, {
            position: 'fixed',
            top: '50%',
            left: '50%',
            transform: 'translate(-50%, -50%)',
            zIndex: '10000',
            padding: '20px',
            width: '600px',
            height: '400px',
            overflowY: 'auto',
            backgroundColor: '#0f1e35',
            color: '#d4d9e4',
            borderTop: '4px solid #4b84e6',
            borderBottom: '4px solid #4b84e6',
            borderRadius: '8px',
            boxShadow: '0 4px 12px rgba(14, 22, 33, .25)',
        });

          backdrop.appendChild(guiContainer);
          document.body.appendChild(backdrop);
        const header = document.createElement('div');
        Object.assign(header.style, {
            display: 'flex',
            justifyContent: 'space-between', // Aligns elements to opposite sides
            alignItems: 'center', // Vertically center the text and button
            marginBottom: '10px',
            width: '100%' // Ensure the container takes up the full width
        });

        const headerText = document.createElement('span');
        headerText.textContent = 'Saved Profiles';
        headerText.style.fontWeight = 'bold';
        headerText.style.fontSize = '1.2em';

        // Create the close button
        const closeButton = document.createElement('button');
        closeButton.innerHTML = '<i class="fa fa-times"></i>'; // Using Font Awesome icon
        Object.assign(closeButton.style, {
            backgroundColor: 'transparent',
            border: 'none',
            fontSize: '1.2em',
            cursor: 'pointer',
        });

        // Close button functionality
        closeButton.addEventListener('click', () => guiContainer.remove());

        // Append both elements to the header
        header.appendChild(headerText);
        header.appendChild(closeButton);


        const table = document.createElement('table');
        table.style.width = '100%';
        table.style.tableLayout = 'fixed'; // Prevents columns from resizing dynamically
        table.style.borderCollapse = 'collapse';

        const thead = document.createElement('thead');
        const headerRow = document.createElement('tr');
        ['Image', 'Title', 'Actions'].forEach((text, index) => {
            const th = document.createElement('th');
            th.textContent = text;
            Object.assign(th.style, {
                borderBottom: '4px solid #4b84e6',
                borderRadius: '2px',
                boxShadow: '0 4px 12px rgba(14, 22, 33, .25)',
                padding: '10px',
                paddingRight: index === 0 ? '30px' : '10px',
                paddingLeft: index === 2 ? '0px' : '10px',
                textAlign: index === 1 ? 'left' : 'center', // Center headers horizontally
                fontWeight: 'bold',
                width: index ===  1 ? '355px' : 'auto'
            });
            headerRow.appendChild(th);
        });

        thead.appendChild(headerRow);
        table.appendChild(thead);

        const tbody = document.createElement('tbody');

        savedProfiles.forEach((profile) => {
            const row = document.createElement('tr');
            Object.assign(row.style, {
                backgroundColor: '#000000',
                borderBottom: '4px solid #07101e',
                color: '#d4d9e4',
            });

            const imgCell = document.createElement('td');
            imgCell.style.padding = '10px';
            imgCell.style.paddingRight = '30px';
            imgCell.style.textAlign = 'center'; // Centers the image horizontally
            const img = document.createElement('img');
            Object.assign(img, {
                src: profile.image,
                alt: 'Profile Picture',
            });
            Object.assign(img.style, {
                width: '60px',
                height: '60px',
                borderRadius: '50%',
                cursor: 'pointer',
            });


            // Click event to open overlay
            img.addEventListener('click', () => {
                const overlay = document.createElement('div');
                Object.assign(overlay.style, {
                    position: 'fixed',
                    top: '0',
                    left: '0',
                    width: '100%',
                    height: '100%',
                    backgroundColor: 'rgba(0, 0, 0, 0.8)',
                    display: 'flex',
                    alignItems: 'center',
                    justifyContent: 'center',
                    zIndex: '10001',
                });

                const fullImg = document.createElement('img');
                fullImg.src = profile.image.replace('-thumb', ''); // Removes "-thumb" from image URL
                Object.assign(fullImg.style, {
                    maxWidth: '90%',
                    maxHeight: '90%',
                    borderRadius: '8px',
                });

                overlay.appendChild(fullImg);

                // Close overlay on click
                overlay.addEventListener('click', () => overlay.remove());
                document.body.appendChild(overlay);
            });

            imgCell.appendChild(img);

            const titleCell = document.createElement('td');
            titleCell.textContent = profile.header;
            titleCell.style.padding = '10px';
            titleCell.style.width = '355px';  // Title column width

            const actionsCell = document.createElement('td');
            actionsCell.style.padding = '10px';
            actionsCell.style.paddingLeft = '0px';
            actionsCell.style.textAlign = 'left'; // Align actions (buttons) to the left

            const actionsContainer = document.createElement('div'); // Action buttons container
            actionsContainer.style.display = 'flex';
            actionsContainer.style.gap = '1px';

            // View icon button
            const viewIcon = document.createElement('button');
            viewIcon.innerHTML = '<i class="fa fa-eye"></i>';
            viewIcon.title = 'View Profile';
            viewIcon.addEventListener('click', () => window.open(profile.url, '_blank'));
            actionsContainer.appendChild(viewIcon);

            // Delete icon button
            const deleteIcon = document.createElement('button');
            deleteIcon.innerHTML = '<i class="fa fa-trash"></i>';
            deleteIcon.title = 'Delete Profile';
            deleteIcon.addEventListener('click', () => {
                if (confirm('Are you sure you want to delete this profile?')) {
                    const updatedProfiles = savedProfiles.filter((p) => p.url !== profile.url);
                    setSavedProfiles(savedProfiles);
                    guiContainer.remove();
                    openSavedProfilesGUI();
                }
            });
            actionsContainer.appendChild(deleteIcon);

            // Edit icon button for changing profile title
            const editIcon = document.createElement('button');
            editIcon.innerHTML = '<i class="fa fa-edit"></i>';
            editIcon.title = 'Edit Title';
            editIcon.addEventListener('click', () => {
                const newTitle = prompt('Edit Profile Title:', profile.header);
                if (newTitle) {
                    profile.header = newTitle;
                    setSavedProfiles(savedProfiles);
                    guiContainer.remove();
                    openSavedProfilesGUI();
                }
            });
            actionsContainer.appendChild(editIcon);

            // Notes icon button for sticky note
            const notesIcon = document.createElement('button');
            notesIcon.innerHTML = '<i class="fa fa-sticky-note"></i>';
            notesIcon.title = 'Add Notes';
            notesIcon.addEventListener('click', () => {
                const notePopup = document.createElement('div');
                Object.assign(notePopup.style, {
                    position: 'fixed',
                    top: '20%',
                    left: '50%',
                    transform: 'translateX(-50%)',
                    zIndex: '10001',
                    backgroundColor: '#fffb8f',
                    color: '#333',
                    padding: '15px',
                    border: '1px solid #ccc',
                    borderRadius: '8px',
                    boxShadow: '0 4px 8px rgba(0,0,0,0.2)',
                    width: '300px',
                    maxHeight: '200px',
                    overflowY: 'auto',
                    fontFamily: 'Arial, sans-serif',
                });

                const noteText = document.createElement('textarea');
                noteText.value = profile.notes || '';
                Object.assign(noteText.style, {
                    width: '100%',
                    height: '100px',
                    border: 'none',
                    backgroundColor: 'transparent',
                    outline: 'none',
                    resize: 'none',
                    fontFamily: 'inherit',
                });

                const saveNoteButton = document.createElement('button');
                saveNoteButton.textContent = 'Save';
                Object.assign(saveNoteButton.style, {
                    marginTop: '10px',
                    marginRight: '10px',
                    padding: '5px 10px',
                    backgroundColor: '#007bff',
                    color: '#fff',
                    border: 'none',
                    borderRadius: '4px',
                    cursor: 'pointer',
                });

                const closeNoteButton = document.createElement('button');
                closeNoteButton.textContent = 'Close';
                Object.assign(closeNoteButton.style, {
                    padding: '5px 10px',
                    backgroundColor: '#6c757d',
                    color: '#fff',
                    border: 'none',
                    borderRadius: '4px',
                    cursor: 'pointer',
                });

                saveNoteButton.addEventListener('click', () => {
                    profile.notes = noteText.value;
                    setSavedProfiles(savedProfiles);
                    alert('Notes saved!');
                    notePopup.remove();
                });

                closeNoteButton.addEventListener('click', () => notePopup.remove());

                notePopup.append(noteText, saveNoteButton, closeNoteButton);
                document.body.appendChild(notePopup);
            });
            actionsContainer.appendChild(notesIcon);


            actionsCell.appendChild(actionsContainer);

            row.append(imgCell, titleCell, actionsCell);
            tbody.appendChild(row);
        });

        table.appendChild(tbody);
        guiContainer.append(header, table);
    };

    // Add "Save Profile" button
    const addSaveButtonToProfiles = () => {
        const profileButtons = document.querySelectorAll('[data-testid="pinUserButton"]');
        profileButtons.forEach((button) => {
            if (button.parentNode.querySelector('#saveProfileButton')) return;

            const saveButton = document.createElement('button');
            Object.assign(saveButton, {
                id: 'saveProfileButton',
                type: 'button',
                'aria-label': 'Save Profile',
                innerHTML: '<i class="fa fa-save controls-icon text-size: 25px;"></i>'
            });
            Object.assign(saveButton, {
                width: '34xp',
                height: '40px',
            })

            saveButton.addEventListener('click', (event) => {
                const profileUrl = window.location.href;
                const profileHeader = document.querySelector('[data-testid="cruiserProfileLabel"].header-text')?.textContent.trim() || 'Unknown Profile';
                const profileImageContainer = document.querySelector('.profile-image-container');
                const profileImageUrl = profileImageContainer?.style.backgroundImage.match(/url\("(.*)"\)/)?.[1];
                const profileImageThumbnail = profileImageUrl?.replace('.jpeg', '-thumb.jpeg') || '';

                showSavePopup(profileUrl, profileHeader, profileImageThumbnail);
            });

            button.parentNode.insertBefore(saveButton, button);
        });
    };

    // Add GUI button to map controls
    const addGUIButtonToMap = () => {
        const travelModeIcon = document.querySelector('[data-testid="travelModeIcon"]');
        if (!travelModeIcon || document.getElementById('openGUIButton')) return;

        const guiButton = document.createElement('button');
      
        Object.assign(guiButton, {
            id: 'openGUIButton',
            type: 'button',
            'aria-label': 'Open Saved Profiles GUI',
            innerHTML: '<i class="fa fa-list controls-icon" style="font-size: 25px;"></i>'
        });

        Object.assign(guiButton.style, {
            width: '50px',
            height: '50px',
            color: '#fff',
            cursor: 'pointer',
        });

        guiButton.addEventListener('click', (event) => {

            event.preventDefault();
            event.stopPropagation();
            openSavedProfilesGUI();
        });

        travelModeIcon.parentNode.insertBefore(guiButton, travelModeIcon);
    }

    // Observe DOM changes
    function debounce(func, wait) {
        let timeout;
        return function (...args) {
            clearTimeout(timeout);
            timeout = setTimeout(() => func.apply(this, args), wait);
        };
    }

    const debouncedObserverCallback = debounce(() => {
        addSaveButtonToProfiles();
        addGUIButtonToMap();
    }, 100);
    const observer = new MutationObserver(debouncedObserverCallback);
    observer.observe(document.body, { childList: true, subtree: true });

})();