CAI CharPic replacer

Replace char avatar in chats with storage, chat-specific.

当前为 2024-12-05 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         CAI CharPic replacer
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  Replace char avatar in chats with storage, chat-specific.
// @match        https://character.ai/chat/*
// @grant        none
// @author       LuxTallis
// @license      MIT
// ==/UserScript==

(function () {
    'use strict';

    const TARGET_IMAGE_BASE_URL = "https://characterai.io/i/400/static/avatars/uploaded/";
    const BROADER_SELECTOR = 'div.group img.object-cover'; // Broad selector for images in div.group
    const EXCLUDED_SELECTOR = '.flex-row-reverse img.object-cover.object-top'; // Specific selector to exclude

    // Get the current chat identifier from the URL
    function getCurrentChatId() {
        const match = window.location.pathname.match(/\/chat\/([^\/]+)/);
        return match ? match[1] : null;
    }

    // Save a custom image URL for the current chat
    function saveCustomImageUrlForChat(chatId, url) {
        const chatImages = JSON.parse(localStorage.getItem('chat_images') || '{}');
        chatImages[chatId] = url;
        localStorage.setItem('chat_images', JSON.stringify(chatImages));
        addToLibrary(url); // Add to the recent library
    }

    // Get the custom image URL for the current chat
    function getCustomImageUrlForChat(chatId) {
        const chatImages = JSON.parse(localStorage.getItem('chat_images') || '{}');
        return chatImages[chatId] || '';
    }

    // Add image to the library of recent images
    function addToLibrary(url) {
        const library = getLibrary();
        if (!url || library.includes(url)) return;
        library.push(url);
        if (library.length > 10) library.shift(); // Limit library to 10 images
        saveLibrary(library);
    }

    // Save the image library to local storage
    function saveLibrary(library) {
        localStorage.setItem('image_library', JSON.stringify(library));
    }

    // Get the image library from local storage
    function getLibrary() {
        return JSON.parse(localStorage.getItem('image_library') || '[]');
    }

    // Replace images with the custom URL for the current chat, excluding unwanted elements
    function replaceImagesForChat(customUrl) {
        const images = document.querySelectorAll(BROADER_SELECTOR);
        images.forEach((image) => {
            // Skip images matching the excluded selector
            if (image.closest(EXCLUDED_SELECTOR)) return;

            if (image.src.startsWith(TARGET_IMAGE_BASE_URL)) {
                image.src = customUrl;
            }
        });
    }

    // Create the button
    const button = document.createElement('button');
    button.innerHTML = '👤';
    button.style.position = 'fixed';
    button.style.top = '110px'; // Positioned below the background script button
    button.style.right = '5px';
    button.style.width = '22px';
    button.style.height = '22px';
    button.style.backgroundColor = '#444';
    button.style.color = 'white';
    button.style.border = 'none';
    button.style.borderRadius = '3px';
    button.style.cursor = 'pointer';
    button.style.fontFamily = 'Montserrat, sans-serif';
    button.style.display = 'flex';
    button.style.justifyContent = 'center';
    button.style.alignItems = 'center';
    button.style.zIndex = '9999';

    // Add the button to the document
    document.body.appendChild(button);

    // Add functionality to set a new image URL for the current chat
    button.addEventListener('click', () => {
        const chatId = getCurrentChatId();
        if (!chatId) {
            alert('Could not determine the chat ID.');
            return;
        }

        // Create popup panel
        const popup = document.createElement('div');
        popup.id = 'imagePopup';
        popup.style.position = 'fixed';
        popup.style.top = '50%';
        popup.style.left = '50%';
        popup.style.transform = 'translate(-50%, -50%)';
        popup.style.backgroundColor = '#1e1e1e';
        popup.style.color = 'white';
        popup.style.borderRadius = '5px';
        popup.style.padding = '20px';
        popup.style.zIndex = '9999';
        popup.style.fontFamily = 'Montserrat, sans-serif';
        popup.style.minWidth = '300px';
        popup.style.maxWidth = '500px';

        const label = document.createElement('label');
        label.textContent = 'Enter Image URL:';
        label.style.display = 'block';
        label.style.marginBottom = '5px';

        const input = document.createElement('input');
        input.type = 'text';
        input.placeholder = 'Enter image URL';
        input.style.width = '100%';
        input.style.marginBottom = '10px';

        const applyButton = document.createElement('button');
        applyButton.textContent = 'Apply Image';
        applyButton.style.marginTop = '10px';
        applyButton.style.padding = '5px 10px';
        applyButton.style.border = 'none';
        applyButton.style.borderRadius = '3px';
        applyButton.style.backgroundColor = '#444';
        applyButton.style.color = 'white';
        applyButton.style.fontFamily = 'Montserrat, sans-serif';
        applyButton.addEventListener('click', () => {
            const url = input.value.trim();
            if (url) {
                saveCustomImageUrlForChat(chatId, url);
                replaceImagesForChat(url);
                popup.remove();
            }
        });

        // Default button (Revert to Default)
        const defaultButton = document.createElement('button');
        defaultButton.textContent = 'Default';
        defaultButton.style.marginTop = '10px';
        defaultButton.style.padding = '5px 10px';
        defaultButton.style.border = 'none';
        defaultButton.style.borderRadius = '3px';
        defaultButton.style.backgroundColor = '#444'; // Same as Apply button
        defaultButton.style.color = 'white';
        defaultButton.style.fontFamily = 'Montserrat, sans-serif';
        defaultButton.style.marginLeft = 'auto'; // Align to the right
        defaultButton.style.cursor = 'pointer';
        defaultButton.addEventListener('click', () => {
            saveCustomImageUrlForChat(chatId, '');  // Clear the custom URL
            replaceImagesForChat('');
            popup.remove();
        });

        // Library for recent images
        const libraryContainer = document.createElement('div');
        libraryContainer.style.marginTop = '10px';
        libraryContainer.style.overflowX = 'auto';
        libraryContainer.style.display = 'flex';
        libraryContainer.style.flexWrap = 'wrap'; // Allow wrapping into multiple lines
        libraryContainer.style.gap = '15px'; // Increased gap between thumbnails
        libraryContainer.style.paddingBottom = '10px';
        libraryContainer.style.borderTop = '1px solid #555';
        libraryContainer.style.paddingTop = '10px';

        const library = getLibrary();
        library.forEach((url) => {
            const imgContainer = document.createElement('div');
            imgContainer.style.position = 'relative';
            imgContainer.style.flex = '0 0 auto'; // Ensures the thumbnail stays at its natural width

            const img = document.createElement('img');
            img.src = url;
            img.alt = 'Preview';
            img.style.width = '100px';  // Twice the size of the previous thumbnails
            img.style.height = '100px';
            img.style.objectFit = 'cover';
            img.style.border = '1px solid #fff';
            img.style.borderRadius = '3px';
            img.style.cursor = 'pointer';
            img.title = url;

            img.addEventListener('click', () => {
                saveCustomImageUrlForChat(chatId, url);
                replaceImagesForChat(url);
                popup.remove();
            });

            // Red remove button in the top-right corner
            const removeButton = document.createElement('button');
            removeButton.textContent = '×';
            removeButton.style.position = 'absolute';
            removeButton.style.top = '5px';
            removeButton.style.right = '5px';
            removeButton.style.backgroundColor = 'red';
            removeButton.style.color = 'white';
            removeButton.style.border = 'none';
            removeButton.style.borderRadius = '50%';
            removeButton.style.cursor = 'pointer';
            removeButton.style.width = '20px';
            removeButton.style.height = '20px';
            removeButton.style.textAlign = 'center';
            removeButton.style.fontSize = '12px';
            removeButton.addEventListener('click', (e) => {
                e.stopPropagation(); // Prevents bubbling up to img's click event
                removeFromLibrary(url);
                renderLibrary(); // Refresh the library after removal
            });

            imgContainer.appendChild(img);
            imgContainer.appendChild(removeButton);
            libraryContainer.appendChild(imgContainer);
        });

        // Function to remove an image from the library
        function removeFromLibrary(url) {
            const library = getLibrary().filter((item) => item !== url);
            saveLibrary(library);
        }

        // Function to render the updated library view
        function renderLibrary() {
            libraryContainer.innerHTML = ''; // Clear current library
            const library = getLibrary();
            library.forEach((url) => {
                const imgContainer = document.createElement('div');
                imgContainer.style.position = 'relative';
                imgContainer.style.flex = '0 0 auto';

                const img = document.createElement('img');
                img.src = url;
                img.alt = 'Preview';
                img.style.width = '100px';
                img.style.height = '100px';
                img.style.objectFit = 'cover';
                img.style.border = '1px solid #fff';
                img.style.borderRadius = '3px';
                img.style.cursor = 'pointer';
                img.title = url;

                img.addEventListener('click', () => {
                    saveCustomImageUrlForChat(chatId, url);
                    replaceImagesForChat(url);
                    popup.remove();
                });

                const removeButton = document.createElement('button');
                removeButton.textContent = '×';
                removeButton.style.position = 'absolute';
                removeButton.style.top = '5px';
                removeButton.style.right = '5px';
                removeButton.style.backgroundColor = 'red';
                removeButton.style.color = 'white';
                removeButton.style.border = 'none';
                removeButton.style.borderRadius = '50%';
                removeButton.style.cursor = 'pointer';
                removeButton.style.width = '20px';
                removeButton.style.height = '20px';
                removeButton.style.textAlign = 'center';
                removeButton.style.fontSize = '12px';
                removeButton.addEventListener('click', (e) => {
                    e.stopPropagation();
                    removeFromLibrary(url);
                    renderLibrary();
                });

                imgContainer.appendChild(img);
                imgContainer.appendChild(removeButton);
                libraryContainer.appendChild(imgContainer);
            });
        }

        popup.appendChild(label);
        popup.appendChild(input);
        popup.appendChild(applyButton);
        popup.appendChild(defaultButton);
        popup.appendChild(libraryContainer);
        document.body.appendChild(popup);

        // Close the popup if clicked outside
        document.addEventListener('click', function closeOnOutsideClick(event) {
            if (!popup.contains(event.target) && !button.contains(event.target)) {
                popup.remove();
                document.removeEventListener('click', closeOnOutsideClick);
            }
        });
    });

    // Load and apply custom image for the current chat
    const chatId = getCurrentChatId();
    const savedImageUrl = getCustomImageUrlForChat(chatId);
    if (savedImageUrl) {
        replaceImagesForChat(savedImageUrl);
    }
})();