Drawaria Anime Drawer Menu

Pastes anime characters directly onto your Drawaria.online canvas from APIs. (Client-side only)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Drawaria Anime Drawer Menu
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  Pastes anime characters directly onto your Drawaria.online canvas from APIs. (Client-side only)
// @author       YouTubeDrawaria
// @include      https://drawaria.online*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=drawaria.online/room/
// @grant        GM_xmlhttpRequest
// @grant        none
// @license      MIT
// ==/UserScript==

(() => {
    'use strict';

    console.log("Anime Bot Drawer (Direct Canvas Paste): Script starting...");

    const EL = (sel) => document.querySelector(sel);

    // No longer need drawing_active or pixelDrawQueue for this method
    let previewCanvas = document.createElement('canvas'); // Used to prepare the image
    let originalCanvas = null; // The game's main canvas
    // data from previewCanvas (pixel data) is no longer directly used for drawing, but loadImage still prepares it.
    // cw, ch will be dimensions of previewCanvas, matching originalCanvas.

    // WebSocket interception is no longer strictly needed for drawing, but can be kept for other potential features
    // or if the game relies on it for other script interactions. For now, let's keep it minimal.
    // window.sockets = [];
    // const originalSend = WebSocket.prototype.send;
    // WebSocket.prototype.send = function (...args) {
    //     if (window.sockets.indexOf(this) === -1) {
    //         window.sockets.push(this);
    //     }
    //     return originalSend.call(this, ...args);
    // };

    function addBoxIcons() {
        console.log("Anime Bot Drawer: addBoxIcons called");
        if (document.querySelector('link[href="https://unpkg.com/[email protected]/css/boxicons.min.css"]')) {
            return;
        }
        let boxicons_link = document.createElement('link');
        boxicons_link.href = 'https://unpkg.com/[email protected]/css/boxicons.min.css';
        boxicons_link.rel = 'stylesheet';
        document.head.appendChild(boxicons_link);
    }

    function CreateStylesheet() {
        console.log("Anime Bot Drawer: CreateStylesheet called");
        if (document.getElementById('AnimeBotDrawerStyles')) {
            return;
        }
        let styleElement = document.createElement('style');
        styleElement.id = 'AnimeBotDrawerStyles';
        styleElement.innerHTML = `
            input[type="number"] { text-align: center; -webkit-appearance: none; -moz-appearance: textfield; }
            .hidden { display: none !important; }
            .anime-drawer-container {
                position: relative; width: 100%; background-color: #ffe0f0; border: 2px solid #ff69b4;
                border-radius: 15px; padding: 10px; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
                margin-top: 10px; font-family: 'Comic Sans MS', 'Chalkboard SE', cursive; color: #8a2be2;
            }
            .anime-drawer-container h3 {
                text-align: center; color: #c71585; margin-bottom: 10px; display: flex;
                align-items: center; justify-content: center; gap: 8px;
            }
            .anime-drawer-row { display: flex; flex-wrap: wrap; gap: 5px; margin-bottom: 5px; justify-content: center; align-items: center;}
            .anime-drawer-row > * { flex: 1 1 auto; min-width: 80px; text-align: center; }
            .anime-border-input {
                border: 1px solid #ffb6c1; border-radius: 8px; padding: 5px;
                background-color: #fff0f5; color: #da70d6; font-size: 14px;
            }
            .anime-drawer-button {
                background-color: #ffc0cb; color: white; border: none; border-radius: 10px;
                padding: 8px 12px; font-size: 16px; cursor: pointer; transition: all 0.3s ease;
                box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); display: flex; align-items: center;
                justify-content: center; gap: 5px;
            }
            .anime-drawer-button:hover { background-color: #ff69b4; transform: translateY(-2px); box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); }
            .anime-drawer-button:active { transform: translateY(0); box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); }
            .anime-drawer-button:disabled { background-color: #f8bbd0; cursor: not-allowed; }
            #animeImageDisplay {
                width: 100%; max-height: 200px; object-fit: contain; border: 1px dashed #ff69b4;
                border-radius: 10px; margin-bottom: 10px; background-color: #fce4ec;
            }
            #toggleAnimeDrawer {
                background-color: #a767f0; color: white; border: none; border-radius: 50%;
                width: 40px; height: 40px; font-size: 24px; cursor: pointer; transition: all 0.3s ease;
                box-shadow: 0 2px 4px rgba(0,0,0,0.2); display: flex; align-items: center;
                justify-content: center; margin-left: 5px; z-index: 10001;
            }
            #toggleAnimeDrawer:hover { background-color: #7d3fbf; transform: rotate(15deg) scale(1.1); }
            #toggleAnimeDrawer.active { background-color: #ff69b4; }
            .anime-category-select {
                border: 1px solid #ffb6c1; border-radius: 8px; padding: 5px;
                background-color: #fff0f5; color: #da70d6; font-size: 14px; cursor: pointer;
            }
            #status_message { font-size: 12px; padding: 3px; margin-top: 5px; flex-basis: 100% !important; text-align: center; color: #c71585; }
        `;
        document.head.appendChild(styleElement);
    }

    async function fetchAnimeImage(category) { // Same as before
        console.log("Anime Bot Drawer: fetchAnimeImage called for", category);
        return new Promise((resolve, reject) => {
            const targetUrl = `https://api.waifu.pics/sfw/${category}`;
            GM_xmlhttpRequest({
                method: "GET", url: targetUrl,
                onload: function(response) {
                    if (response.status >= 200 && response.status < 300) {
                        try {
                            const result = JSON.parse(response.responseText);
                            if (result && result.url) { resolve(result.url); }
                            else { reject(new Error('API response did not contain a URL.')); }
                        } catch (e) { reject(e); }
                    } else { reject(new Error(`HTTP error! status: ${response.statusText}`)); }
                },
                onerror: function(response) { reject(new Error(`Network error: ${response.statusText || response.error}`)); }
            });
        });
    }

    async function loadImage(url, animeImageDisplayElement) { // Same as before, prepares previewCanvas
        console.log("Anime Bot Drawer: loadImage called for URL:", url);
        return new Promise((resolve, reject) => {
            GM_xmlhttpRequest({
                method: 'GET', url: url, responseType: 'blob',
                onload: function(response) {
                    if (response.status === 200) {
                        var img = new Image();
                        var objectURL = URL.createObjectURL(response.response);
                        img.onload = () => {
                            if (!originalCanvas) {
                                reject(new Error("Main game canvas not found during loadImage.onload.")); return;
                            }
                            // Set previewCanvas dimensions to match the game's canvas
                            previewCanvas.width = originalCanvas.width;
                            previewCanvas.height = originalCanvas.height;

                            var ctx = previewCanvas.getContext('2d');
                            ctx.clearRect(0, 0, previewCanvas.width, previewCanvas.height); // Clear before drawing

                            // Calculate dimensions to draw the image centered and scaled to fit onto previewCanvas
                            let imgAspectRatio = img.width / img.height;
                            let canvasAspectRatio = previewCanvas.width / previewCanvas.height;
                            let drawWidth, drawHeight, offsetX_preview, offsetY_preview;

                            if (imgAspectRatio > canvasAspectRatio) {
                                drawWidth = previewCanvas.width;
                                drawHeight = previewCanvas.width / imgAspectRatio;
                                offsetX_preview = 0;
                                offsetY_preview = (previewCanvas.height - drawHeight) / 2;
                            } else {
                                drawHeight = previewCanvas.height;
                                drawWidth = previewCanvas.height * imgAspectRatio;
                                offsetY_preview = 0;
                                offsetX_preview = (previewCanvas.width - drawWidth) / 2;
                            }
                            ctx.drawImage(img, offsetX_preview, offsetY_preview, drawWidth, drawHeight);
                            console.log(`Anime Bot Drawer: Image drawn to previewCanvas (size ${previewCanvas.width}x${previewCanvas.height})`);
                            if (animeImageDisplayElement) {
                                animeImageDisplayElement.src = objectURL;
                                animeImageDisplayElement.classList.remove('hidden');
                            }
                            resolve(); // Resolve once previewCanvas is ready
                        };
                        img.onerror = (e) => { URL.revokeObjectURL(objectURL); reject(e); };
                        img.src = objectURL;
                    } else { reject(new Error(`Failed to download image: ${response.statusText}`)); }
                },
                onerror: function(response) { reject(new Error(`Network error downloading image: ${response.statusText || response.error}`));}
            });
        });
    }

    function AnimeBotDrawerEngine() {
        console.log("Anime Bot Drawer: AnimeBotDrawerEngine called.");
        let animeDrawerContainer = document.getElementById('AnimeBotDrawerContainer');
        if (animeDrawerContainer) { return; } // Already initialized
        animeDrawerContainer = document.createElement('div');
        animeDrawerContainer.id = 'AnimeBotDrawerContainer';
        animeDrawerContainer.className = 'anime-drawer-container hidden';
        const chatMessagesElement = document.getElementById('chatbox_messages');
        if (chatMessagesElement) { chatMessagesElement.after(animeDrawerContainer); }
        else { document.body.appendChild(animeDrawerContainer); }

        CreateToggleButton(animeDrawerContainer);
        AnimeImageControls(animeDrawerContainer);
    }

    function CreateToggleButton(containerToToggle) { // Same as before
        console.log("Anime Bot Drawer: CreateToggleButton called.");
        let target = document.getElementById('chatbox_textinput');
        if (!target) { console.error("Anime Bot Drawer: Chat input for toggle button not found."); return; }
        if (document.getElementById('toggleAnimeDrawer')) { return; }

        let btncontainer = document.createElement('div');
        btncontainer.className = 'input-group-append';
        let togglebtn = document.createElement('button');
        togglebtn.id = 'toggleAnimeDrawer';
        togglebtn.innerHTML = '<i class="bx bxs-paint"></i>';
        togglebtn.title = "Toggle Anime Drawer";
        togglebtn.type = "button";
        togglebtn.addEventListener('click', (e) => {
            e.preventDefault();
            togglebtn.classList.toggle('active');
            if (containerToToggle) { containerToToggle.classList.toggle('hidden'); }
        });
        btncontainer.appendChild(togglebtn);
        target.after(btncontainer);
        console.log("Anime Bot Drawer: Toggle button added.");
    }

    function AnimeImageControls(container) {
        console.log("Anime Bot Drawer: AnimeImageControls called.");
        let controlsDiv = document.createElement('div');
        controlsDiv.innerHTML = `
            <h3>Anime Drawer Menu <i class='bx bxs-heart'></i></h3>
            <img id="animeImageDisplay" src="" alt="Anime Image Preview" class="hidden">
            <div class="anime-drawer-row">
                <select id="animeCategorySelect" class="anime-category-select"></select>
                <button id="loadAnimeBtn" class="anime-drawer-button"><i class='bx bxs-image-add'></i> Load Anime</button>
            </div>
            <div class="anime-drawer-row">
                 <label for="engine_offset_x" class="anime-border-input" title="Offset X (% of canvas width)">Offset X (%):</label>
                <input type="number" id="engine_offset_x" min="-100" max="100" value="0" class="anime-border-input">
                <label for="engine_offset_y" class="anime-border-input" title="Offset Y (% of canvas height)">Offset Y (%):</label>
                <input type="number" id="engine_offset_y" min="-100" max="100" value="0" class="anime-border-input">
            </div>
            <div class="anime-drawer-row">
                <button id="pasteToCanvasBtn" class="anime-drawer-button"><i class='bx bxs-image'></i> Paste to Canvas</button>
                <button id="clearPreviewBtn" class="anime-drawer-button" title="Clear the preview image and any pasted image (by drawing white over area)"><i class='bx bx-eraser'></i> Clear Preview</button>
            </div>
            <div class="anime-drawer-row">
                 <span id="status_message"></span>
            </div>
        `;
        container.appendChild(controlsDiv);

        const animeImageDisplay = EL('#animeImageDisplay');
        const animeCategorySelect = EL('#animeCategorySelect');
        const loadAnimeBtn = EL('#loadAnimeBtn');
        const pasteToCanvasBtn = EL('#pasteToCanvasBtn');
        const clearPreviewBtn = EL('#clearPreviewBtn');
        const statusMessage = EL('#status_message');

        const categories = [ /* ... same categories ... */
            'waifu', 'neko', 'shinobu', 'megumin', 'bully', 'cuddle', 'cry',
            'hug', 'awoo', 'kiss', 'lick', 'pat', 'smug', 'bonk', 'yeet',
            'blush', 'smile', 'wave', 'highfive', 'handhold', 'nom', 'bite',
            'glomp', 'slap', 'kill', 'kick', 'happy', 'wink', 'poke', 'dance', 'cringe'
        ];
        categories.forEach(cat => {
            const option = document.createElement('option');
            option.value = cat;
            option.textContent = cat.charAt(0).toUpperCase() + cat.slice(1);
            animeCategorySelect.appendChild(option);
        });

        loadAnimeBtn.addEventListener('click', async () => {
            const category = animeCategorySelect.value;
            loadAnimeBtn.disabled = true; loadAnimeBtn.innerHTML = '<i class="bx bx-loader-alt bx-spin"></i> Loading...';
            statusMessage.textContent = 'Fetching image URL...';
            try {
                const imageUrl = await fetchAnimeImage(category);
                if (imageUrl) {
                    statusMessage.textContent = 'Image URL fetched. Loading image data...';
                    await loadImage(imageUrl, animeImageDisplay); // loadImage now prepares previewCanvas
                    statusMessage.textContent = 'Image loaded. Ready to paste.';
                } else {
                    statusMessage.textContent = 'Failed to get image URL.';
                }
            } catch (error) {
                console.error('Anime Bot Drawer: Error in loadAnimeBtn click:', error);
                statusMessage.textContent = 'Error loading image. See console.';
            } finally {
                loadAnimeBtn.disabled = false; loadAnimeBtn.innerHTML = '<i class="bx bxs-image-add"></i> Load Anime';
            }
        });

        pasteToCanvasBtn.addEventListener('click', () => {
            if (!previewCanvas || previewCanvas.width === 0 || previewCanvas.height === 0) {
                statusMessage.textContent = 'Load an image first to prepare it on the preview canvas.';
                alert('Please load an anime image first!');
                return;
            }
            if (!originalCanvas) {
                 statusMessage.textContent = 'Game canvas not found.';
                 alert('Error: Game canvas not found!');
                 return;
            }

            const gameCtx = originalCanvas.getContext('2d');
            if (!gameCtx) {
                statusMessage.textContent = 'Could not get game canvas context.';
                alert('Error: Could not get game canvas context!');
                return;
            }

            const offsetXPercent = parseFloat(EL('#engine_offset_x').value) || 0;
            const offsetYPercent = parseFloat(EL('#engine_offset_y').value) || 0;

            const dx = (offsetXPercent / 100) * originalCanvas.width;
            const dy = (offsetYPercent / 100) * originalCanvas.height;

            try {
                // previewCanvas already contains the scaled and centered image matching originalCanvas dimensions
                gameCtx.drawImage(previewCanvas, dx, dy);
                statusMessage.textContent = 'Image pasted to your canvas!';
                console.log(`Anime Bot Drawer: Pasted previewCanvas to game canvas at ${dx.toFixed(0)}, ${dy.toFixed(0)}`);
            } catch (e) {
                statusMessage.textContent = 'Error pasting image. See console.';
                console.error("Anime Bot Drawer: Error during gameCtx.drawImage():", e);
                alert("An error occurred while pasting the image. Check console.");
            }
        });

        clearPreviewBtn.addEventListener('click', () => {
            animeImageDisplay.src = '';
            animeImageDisplay.classList.add('hidden');
            if (previewCanvas) {
                const prevCtx = previewCanvas.getContext('2d');
                prevCtx.clearRect(0, 0, previewCanvas.width, previewCanvas.height);
            }
            // Optionally, attempt to clear the area on the main canvas
            // This is imperfect as it just draws white.
            if(originalCanvas && previewCanvas && previewCanvas.width > 0 && previewCanvas.height > 0){
                const gameCtx = originalCanvas.getContext('2d');
                 const offsetXPercent = parseFloat(EL('#engine_offset_x').value) || 0;
                 const offsetYPercent = parseFloat(EL('#engine_offset_y').value) || 0;
                 const dx = (offsetXPercent / 100) * originalCanvas.width;
                 const dy = (offsetYPercent / 100) * originalCanvas.height;
                gameCtx.fillStyle = 'white'; // Assuming default bg is white
                gameCtx.fillRect(dx, dy, previewCanvas.width, previewCanvas.height); // Draw white over the area
                 statusMessage.textContent = 'Preview cleared and area on canvas whited out.';
            } else {
                 statusMessage.textContent = 'Preview cleared.';
            }
            console.log("Anime Bot Drawer: Preview cleared.");
        });
    }


    function initializeScript() {
        console.log("Anime Bot Drawer: initializeScript called.");
        originalCanvas = document.getElementById('canvas'); // Get the game's canvas

        if (!originalCanvas) {
            console.log("Anime Bot Drawer: Game canvas ('canvas') not found yet. Retrying...");
            setTimeout(initializeScript, 1000); return;
        }
        console.log(`Anime Bot Drawer: Game canvas found. Dimensions: ${originalCanvas.width}x${originalCanvas.height}`);

        // Ensure previewCanvas is set up, though its dimensions will be set relative to originalCanvas in loadImage
        previewCanvas.width = originalCanvas.width; // Initialize with game canvas size
        previewCanvas.height = originalCanvas.height;


        const chatInput = document.getElementById('chatbox_textinput');
        const chatMessages = document.getElementById('chatbox_messages');
        if (!chatInput || !chatMessages) {
            console.log("Anime Bot Drawer: Chat elements not found yet. Retrying...");
            setTimeout(initializeScript, 1000); return;
        }

        if (!document.getElementById('AnimeBotDrawerContainer')) {
            addBoxIcons();
            CreateStylesheet();
            AnimeBotDrawerEngine();
            console.log('Anime Bot Drawer (Direct Canvas Paste) UI Initialized!');
        }
    }

    if (document.readyState === "complete" || document.readyState === "interactive") {
        initializeScript();
    } else {
        window.addEventListener('DOMContentLoaded', initializeScript);
    }

})();